From c8ada8fdc3ab5ed7b8e97a4c795db7b153c345aa Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Thu, 21 May 2026 09:36:51 -0400 Subject: [PATCH 01/50] visualizing meter --- .gitignore | 1 + nirukta/render.py | 35 ++++++++++++++++++++++++ nirukta/timelines/introduce_sloka.py | 41 +++++++++++++++++++++++----- pyproject.toml | 1 + test.py | 36 ++++++++++++++++++++++++ uv.lock | 36 ++++++++++++++++++++++++ 6 files changed, 143 insertions(+), 7 deletions(-) create mode 100644 test.py diff --git a/.gitignore b/.gitignore index d01e00b..45d7a06 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ videos __pycache__ .devenv .cache +vidyut_data diff --git a/nirukta/render.py b/nirukta/render.py index 158019f..e4ed315 100644 --- a/nirukta/render.py +++ b/nirukta/render.py @@ -4,7 +4,9 @@ GREEN, LEFT, MED_SMALL_BUFF, + ORANGE, RED, + TEAL, UL, Aligned, Cmpt_Rgbas, @@ -217,6 +219,39 @@ def sloka_group(sloka: Sloka) -> Group[TypstText]: return group +def sloka_group_chandas(sloka: Sloka, chandas) -> Group: + """Like sloka_group() but each akshara is colored by prosodic weight. + + Guru (heavy) → ORANGE | Laghu (light) → TEAL + Produces an identical layout to sloka_group() so LenientTransformMatchingDiff + can match glyphs purely as a color change. + """ + group = [] + for li, line in enumerate(sloka.lines): + sanskritcode = "" + for vi, vAkya in enumerate(line.vAkyAni): + utterance_code = "" + for token in vAkya.tokens: + if isinstance(token, str): + utterance_code += text_box(token, WHITE) + else: + match = chandas.classify(token.slp1) + for pada in match.aksharas: + for akshara in pada: + color = ORANGE if akshara.weight == "G" else TEAL + utterance_code += typst_code( + akshara.text, Language.SANSKRIT, color + ) + utterance_code += " " + sanskritcode += f"{utterance_code} " + + group.append(TypstText(set_font(sanskritcode, INTRO_FONT), scale=SCALE)) + + group = Group(*group) + group.points.arrange(DOWN) + return group + + def sloka_group_english(sloka: Sloka) -> Group[TypstText]: group = [] diff --git a/nirukta/timelines/introduce_sloka.py b/nirukta/timelines/introduce_sloka.py index e0c5965..89fafc3 100644 --- a/nirukta/timelines/introduce_sloka.py +++ b/nirukta/timelines/introduce_sloka.py @@ -1,8 +1,24 @@ +from pathlib import Path from typing import Optional + +import vidyut from janim.imports import DOWN, YELLOW, FadeOut, Group, Timeline, TypstText, Wait, Write -from nirukta.models import Language, Sloka -from nirukta.render import sloka_group, set_font, typst_code +from vidyut.chandas import Chandas + from nirukta.constants import INTRO_FONT, SCALE +from nirukta.models import Language, Sloka +from nirukta.render import FlatAligned, set_font, sloka_group, sloka_group_chandas, typst_code +from nirukta.timelines.transform import LenientTransformMatchingDiff + +_DATA_DIR = Path(__file__).parents[2] / "vidyut_data" +_METERS_TSV = _DATA_DIR / "chandas" / "meters.tsv" + + +def _get_chandas() -> Chandas: + if not _METERS_TSV.exists(): + _DATA_DIR.mkdir(exist_ok=True) + vidyut.download_data(_DATA_DIR) + return Chandas(str(_METERS_TSV)) class IntroduceSloka(Timeline): @@ -24,20 +40,31 @@ def construct(self): for line in sloka_g: self.play(Write(line, duration=4.0)) + # After full write-on, color each akshara by prosodic weight + chandas = _get_chandas() + sloka_chandas = sloka_group_chandas(self.sloka, chandas) + self.play( + FlatAligned( + *( + LenientTransformMatchingDiff(sloka_g[i], sloka_chandas[i], duration=0.6) + for i in range(len(sloka_g)) + ), + ) + ) + self.play(Wait(2.0)) + if self.citation is not None and self.citation != "sloka": citation_text = TypstText( set_font(typst_code(self.citation, Language.SANSKRIT), INTRO_FONT), scale=SCALE, ) print(citation_text.text) - citation_text.points.next_to(sloka_g, DOWN) + citation_text.points.next_to(sloka_chandas, DOWN) for animation in [ - Wait(2.0), Write(citation_text, duration=1.0), Wait(1.0), - FadeOut(Group(sloka_g, citation_text)), + FadeOut(Group(sloka_chandas, citation_text)), ]: self.play(animation) else: - self.play(Wait(1.0)) - self.play(FadeOut(sloka_g)) + self.play(FadeOut(sloka_chandas)) diff --git a/pyproject.toml b/pyproject.toml index 28b23c2..d91bd95 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,6 +9,7 @@ dependencies = [ "dill>=0.4.1", "janim[gui]>=4.1.0", "parsimonious>=0.11.0", + "vidyut>=0.4.0", ] [build-system] diff --git a/test.py b/test.py new file mode 100644 index 0000000..d06660f --- /dev/null +++ b/test.py @@ -0,0 +1,36 @@ +import vidyut +from pathlib import Path +from vidyut.chandas import Chandas +from vidyut.lipi import transliterate, Scheme + +DATA_DIR = Path(__file__).parent / "vidyut_data" +METERS_TSV = DATA_DIR / "chandas" / "meters.tsv" + +if not METERS_TSV.exists(): + DATA_DIR.mkdir(exist_ok=True) + vidyut.download_data(DATA_DIR) + +# Lowercase so conventional caps don't confuse the IAST parser +padas_iast = [ + "vakratuṇḍa mahākāya", + "sūryakoṭi samaprabha", + "nirvighnaṃ kuru me deva", + "sarva kāryeṣu sarvadā", +] + +c = Chandas(str(METERS_TSV)) + +# Sanity check: known example from the vidyut docs (expects vasantatilakā) +_doc_match = c.classify("mAtaH samastajagatAM maDukEwaBAreH") +assert _doc_match.padya == "vasantatilakA", f"Library sanity check failed: got {_doc_match.padya}" +print("Sanity check passed: vasantatilakā identified correctly.\n") + +for i, iast in enumerate(padas_iast): + slp1 = transliterate(iast, Scheme.Iast, Scheme.Slp1) + print(f"Pada {i + 1} (SLP1): {slp1}") + match = c.classify(slp1) + print(f" Meter identified: {match.padya}") + for pada in match.aksharas: + for akshara in pada: + print(f" {akshara.text:12} {akshara.weight}") + print() diff --git a/uv.lock b/uv.lock index 6510ed3..02e6df8 100644 --- a/uv.lock +++ b/uv.lock @@ -450,6 +450,7 @@ dependencies = [ { name = "dill" }, { name = "janim", extra = ["gui"] }, { name = "parsimonious" }, + { name = "vidyut" }, ] [package.metadata] @@ -458,6 +459,7 @@ requires-dist = [ { name = "dill", specifier = ">=0.4.1" }, { name = "janim", extras = ["gui"], specifier = ">=4.1.0" }, { name = "parsimonious", specifier = ">=0.11.0" }, + { name = "vidyut", specifier = ">=0.4.0" }, ] [[package]] @@ -989,6 +991,40 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, ] +[[package]] +name = "vidyut" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/69/b5/6876b3f807ac38812c6e29eb43738d28a6a21c2aed456486e654997efcdd/vidyut-0.4.0.tar.gz", hash = "sha256:fb776ec751e66c8d44bd94ef71008f9ea05284f51055aa009cfaef33ccf5f454", size = 943090, upload-time = "2025-01-22T07:28:01.479Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/92/dfd27d8d4fa19d22001533399396a2fe9ec57c97bbc50c5352150ca0a96d/vidyut-0.4.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:cae462b70772e30029c4e2f0c1ee6a39ae4bff43ac755998efb3ca532c97147f", size = 2512816, upload-time = "2025-01-22T07:27:26.362Z" }, + { url = "https://files.pythonhosted.org/packages/89/01/40f37a8a821b434dc021773fad9ef4191e3a5b579cbbbde1cf41e18073b0/vidyut-0.4.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b8db5d2bed6331b0af14b200876d36d5f3b9705c186204ebdb5c4d1c7e9da1df", size = 2439225, upload-time = "2025-01-22T07:27:22.248Z" }, + { url = "https://files.pythonhosted.org/packages/6c/2c/7d7d4cfa59e36927f603ef9844a664ef7e51ed5a00534e4882f3def31146/vidyut-0.4.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0115fdada49c40b6affe4622ff1d2f0ca8761d8235544e713a25b4f18a8a0366", size = 18587289, upload-time = "2025-01-22T07:26:35.987Z" }, + { url = "https://files.pythonhosted.org/packages/56/f3/5b9f5bef2218f5dc5d3078b3269be8616f28ac6923c81e1dbee6ac342cff/vidyut-0.4.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:981d7230696cb0ba071353aa12b113b3c23a800812c5afe5617e74aca5774561", size = 17850731, upload-time = "2025-01-22T07:26:43.513Z" }, + { url = "https://files.pythonhosted.org/packages/9a/f8/54a4768445bdff0bde149f6af8233f31aa08c1d41d4590eb50ce4b189192/vidyut-0.4.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b87107f21f924da74e2472d0cd4982d737dc77315a7fe192cd9a4df198b1515", size = 18636245, upload-time = "2025-01-22T07:26:51.119Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f7/af88b4b818d22d040ff54e3e5f70b640b6ec99d6bb7eb34f66a9f0e545dc/vidyut-0.4.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b5bed1a4bf48211e0609c7e383d3165aa6a316ee75b3f9467f42189e5beb31a4", size = 23876066, upload-time = "2025-01-22T07:26:59.548Z" }, + { url = "https://files.pythonhosted.org/packages/91/16/c36e045941850ca6379349aa19f1acf9e7f6a271e7b5fa896a3602bdb0e1/vidyut-0.4.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cd81e600ee8c686619c7d6afa555a93f09badbd0724067e44488eec75771607", size = 19113361, upload-time = "2025-01-22T07:27:16.69Z" }, + { url = "https://files.pythonhosted.org/packages/0c/8a/87c3cf5a0ce4925d70bbdd1d65d0aaaf34ea0f2cd0c560e834befc8352fd/vidyut-0.4.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe7dfbfd1c48ddea6d5f74665dad9b9e7d5322d43aaad982f7a25c4dd9e58783", size = 18259051, upload-time = "2025-01-22T07:27:08.253Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ee/472ed3dd517279dc4b5271383f37aea7f30eb2fddeceb53cf87e4f1f4a04/vidyut-0.4.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:990c5665a73da5156a7b2f78d425d47977938486f30eba3b6fab216c3611eb54", size = 18630041, upload-time = "2025-01-22T07:27:31.817Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b2/5cfd46f557cf53e5ab562d0095008ddad9c55f01fd6be3987546856c2567/vidyut-0.4.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:6a5561e74ee614482c57489de0aa794601398e827a1c2674536c694d1f7668af", size = 18109898, upload-time = "2025-01-22T07:27:38.771Z" }, + { url = "https://files.pythonhosted.org/packages/8e/ad/e22478bcbe41f5606e22a3c0d5fc10a2c77a887c677bb95090e25ade605c/vidyut-0.4.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d82ab6e16b03dbd9c1e9c30341cc3dd4b5f866d0f3f8b8b9f4a56086db35808e", size = 18114763, upload-time = "2025-01-22T07:27:48.202Z" }, + { url = "https://files.pythonhosted.org/packages/bd/ae/e326679ec630ca927c96fdedad77378e0092c6d48a328119200d1ed61b31/vidyut-0.4.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a60556987be9957082c2432bfc36ab2615bed53f4c70c1591b552e721195c03b", size = 19128726, upload-time = "2025-01-22T07:27:54.81Z" }, + { url = "https://files.pythonhosted.org/packages/67/f5/01d5731f0fa8e6f80e84e7b151cd7c337f16b6e0f448cff040cce2b9a411/vidyut-0.4.0-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:26538146d6c2ac251bebf4133d90f867b3fb2c4c8a9a3795dd5763852c5dc239", size = 2537951, upload-time = "2025-01-22T07:27:28.521Z" }, + { url = "https://files.pythonhosted.org/packages/8d/7f/290ddcdeaa2b247c9a38e36644346ce5e7749362915d184efc428c8a4551/vidyut-0.4.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:e8f79aeb02c2be934d158b1d620260f95ca6f683707b73155b346804d58924a2", size = 2460025, upload-time = "2025-01-22T07:27:24.297Z" }, + { url = "https://files.pythonhosted.org/packages/46/22/8ad1faee4ef357d00ac00f2d6e15074d102e808d0a0236bc8297f98d675b/vidyut-0.4.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea709c0f5abeaa8edfa6f6eaa53ee656fd2c11c604e7fd237fe5d9a85c6eae4b", size = 18448000, upload-time = "2025-01-22T07:26:39.501Z" }, + { url = "https://files.pythonhosted.org/packages/53/06/ab1dda2c6c4f6094acd66579614b2b5c0d9c11013bf599694df58c8a2e12/vidyut-0.4.0-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b4ba80337bf9293ee7c8f2d9cc3cee16d6fa769391c5f492d339884ade0ad4e", size = 17713239, upload-time = "2025-01-22T07:26:47.314Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d4/26d9775e1e5e7b120515957987d435559f15e52aee51055008b39cfe950e/vidyut-0.4.0-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a817fa7c2065ad9f764c79bfbeb84da8bcd74a5b451591cddc1715fc3e43ad6", size = 18504489, upload-time = "2025-01-22T07:26:54.98Z" }, + { url = "https://files.pythonhosted.org/packages/d9/c0/14f07e153690f043edbfbc0f5b8970b1a07b0d50a5c7839440760b148fda/vidyut-0.4.0-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3e6bec7f4d5d601b055242898274d2f5506166160dcf3130faf24138f902a9c0", size = 23855627, upload-time = "2025-01-22T07:27:03.876Z" }, + { url = "https://files.pythonhosted.org/packages/10/0f/62cb7aea1bddadc989854ca0b548466b0d55383b46c0247431b775334436/vidyut-0.4.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:844d213d283218708e4aa5723d656e270a0b639e6ab9af3af96c62b760868b1f", size = 18972888, upload-time = "2025-01-22T07:27:19.894Z" }, + { url = "https://files.pythonhosted.org/packages/fd/a1/8bbab20dcb4d72270be7a8f4a58291d42b85ad697a79ca426ab2e4d43e14/vidyut-0.4.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:234fd0afe1e2ae951c1dcc8476730df101b8b7d5b41607f1835170400e2a0ec7", size = 18135397, upload-time = "2025-01-22T07:27:12.812Z" }, + { url = "https://files.pythonhosted.org/packages/1c/5d/a2353ad30ff232a39d346a280e0cd7aa6812d6361c90193b02bda2320131/vidyut-0.4.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f8c4fbbdf7da16f57995e9b88ba92665cc941babd2233ddd4ffdb2a38de8513b", size = 18489174, upload-time = "2025-01-22T07:27:35.174Z" }, + { url = "https://files.pythonhosted.org/packages/fa/11/6328d14e87207bd23729fdd3ce5f8dea448d66c43fdb36abff7ffefab605/vidyut-0.4.0-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:d1d36ea4bdca42f8faafe57faab832052499f32975ce1cafaf042f56a9053690", size = 17987388, upload-time = "2025-01-22T07:27:44.511Z" }, + { url = "https://files.pythonhosted.org/packages/58/d7/96d1512b18ca68203a0b8c87b6056e2f16d2f9410d60829bc3bba94b715a/vidyut-0.4.0-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:1dbc097ad23a75c951f35cc9687b43bc73f9bb7cd5d1825d3f48247f2963219e", size = 18002635, upload-time = "2025-01-22T07:27:51.52Z" }, + { url = "https://files.pythonhosted.org/packages/91/0b/3e81e8447495b7b0a4e441c024cff55cc6a1f739787f72f3b8d3bd436be1/vidyut-0.4.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:07427a0a9b29e86d9ae92972ef4652f657c018cfd5fe761652d71c569ab39212", size = 18982852, upload-time = "2025-01-22T07:27:59.021Z" }, + { url = "https://files.pythonhosted.org/packages/2d/f0/0730b592ed5623a149ff365d9b02df078e6282ac8f5c2137d116f6ddccfe/vidyut-0.4.0-cp37-abi3-win32.whl", hash = "sha256:a60e512b58970b75f4415d7782724f7294a4e12568de373ad5bf289d0f43def1", size = 1963786, upload-time = "2025-01-22T07:28:04.746Z" }, + { url = "https://files.pythonhosted.org/packages/b2/93/72e17f386de3fdd416063de14a900c922cfcc5e38a6d829eb434f96dcb5c/vidyut-0.4.0-cp37-abi3-win_amd64.whl", hash = "sha256:15854f9a4cddaea0539bd02e727456946db4f39b417f83af2f9a8c9db03f1e52", size = 2103011, upload-time = "2025-01-22T07:28:03.248Z" }, +] + [[package]] name = "wrapt" version = "2.1.2" From cb6e86bd15792c3b22389627b72c1800b575301f Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Thu, 21 May 2026 12:02:36 -0400 Subject: [PATCH 02/50] more experimentation with chandas --- nirukta/render.py | 54 ++++++++++++++-------------- nirukta/timelines/introduce_sloka.py | 14 ++++++-- 2 files changed, 39 insertions(+), 29 deletions(-) diff --git a/nirukta/render.py b/nirukta/render.py index e4ed315..10be50d 100644 --- a/nirukta/render.py +++ b/nirukta/render.py @@ -1,23 +1,19 @@ from janim.imports import ( C_LABEL_ANIM_DEFAULT, C_LABEL_ANIM_OUT, - GREEN, + BLUE_E, + RED_E, LEFT, MED_SMALL_BUFF, - ORANGE, - RED, - TEAL, UL, Aligned, Cmpt_Rgbas, DataUpdater, - ItemUpdater, RateFunc, Rect, SupportsAnim, AnimGroup, Succession, - Indicate, DOWN, UP, FadeOut, @@ -219,33 +215,37 @@ def sloka_group(sloka: Sloka) -> Group[TypstText]: return group -def sloka_group_chandas(sloka: Sloka, chandas) -> Group: - """Like sloka_group() but each akshara is colored by prosodic weight. +def sloka_group_chandas(sloka: Sloka, chandas, blank: bool = False) -> Group: + """Each akshara in a grid cell; cell background encodes prosodic weight. - Guru (heavy) → ORANGE | Laghu (light) → TEAL - Produces an identical layout to sloka_group() so LenientTransformMatchingDiff - can match glyphs purely as a color change. + Guru (heavy) → ORANGE background | Laghu (light) → TEAL background + Text is white throughout. One row per sloka line. + When blank=True, all box backgrounds are black (invisible) — same SVG + structure but no visible color, suitable as an animation intermediate. """ group = [] for li, line in enumerate(sloka.lines): - sanskritcode = "" - for vi, vAkya in enumerate(line.vAkyAni): - utterance_code = "" + cells = [] + for vAkya in line.vAkyAni: for token in vAkya.tokens: if isinstance(token, str): - utterance_code += text_box(token, WHITE) - else: - match = chandas.classify(token.slp1) - for pada in match.aksharas: - for akshara in pada: - color = ORANGE if akshara.weight == "G" else TEAL - utterance_code += typst_code( - akshara.text, Language.SANSKRIT, color - ) - utterance_code += " " - sanskritcode += f"{utterance_code} " - - group.append(TypstText(set_font(sanskritcode, INTRO_FONT), scale=SCALE)) + continue + match = chandas.classify(token.slp1) + for pada in match.aksharas: + for akshara in pada: + bg = BLUE_E if akshara.weight == "G" else RED_E + deva = transform_text(akshara.text, Language.SANSKRIT) + fill = ( + "rgb(0, 0, 0, 0)" if blank else f'rgb("{bg.lstrip("#")}")' + ) + cells.append( + f"box(fill: {fill}, inset: 6pt, radius: 3pt)" + f"[#align(center + horizon)[#text(fill: white)[{deva}]]]" + ) + + n = len(cells) + grid_code = f"#grid(columns: (auto,) * {n}, gutter: 3pt, {', '.join(cells)})" + group.append(TypstText(set_font(grid_code, INTRO_FONT), scale=SCALE)) group = Group(*group) group.points.arrange(DOWN) diff --git a/nirukta/timelines/introduce_sloka.py b/nirukta/timelines/introduce_sloka.py index 89fafc3..d3e0cec 100644 --- a/nirukta/timelines/introduce_sloka.py +++ b/nirukta/timelines/introduce_sloka.py @@ -40,13 +40,23 @@ def construct(self): for line in sloka_g: self.play(Write(line, duration=4.0)) - # After full write-on, color each akshara by prosodic weight + # After full write-on, first move glyphs into grid boxes (no color)… chandas = _get_chandas() + sloka_chandas_blank = sloka_group_chandas(self.sloka, chandas, blank=True) sloka_chandas = sloka_group_chandas(self.sloka, chandas) self.play( FlatAligned( *( - LenientTransformMatchingDiff(sloka_g[i], sloka_chandas[i], duration=0.6) + LenientTransformMatchingDiff(sloka_g[i], sloka_chandas_blank[i], duration=0.6) + for i in range(len(sloka_g)) + ), + ) + ) + # …then reveal the prosodic colors + self.play( + FlatAligned( + *( + LenientTransformMatchingDiff(sloka_chandas_blank[i], sloka_chandas[i], duration=0.6) for i in range(len(sloka_g)) ), ) From 0d4906cb41e3b82ef35e4945b1055dc9c79e7e09 Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Thu, 21 May 2026 12:16:29 -0400 Subject: [PATCH 03/50] unified grid --- nirukta/render.py | 21 +++++++++++++-------- nirukta/timelines/introduce_sloka.py | 18 ++---------------- 2 files changed, 15 insertions(+), 24 deletions(-) diff --git a/nirukta/render.py b/nirukta/render.py index 10be50d..3014819 100644 --- a/nirukta/render.py +++ b/nirukta/render.py @@ -219,13 +219,12 @@ def sloka_group_chandas(sloka: Sloka, chandas, blank: bool = False) -> Group: """Each akshara in a grid cell; cell background encodes prosodic weight. Guru (heavy) → ORANGE background | Laghu (light) → TEAL background - Text is white throughout. One row per sloka line. + Text is white throughout. One row per pada (hardcoded to 8 aksharas for anuṣṭubh). When blank=True, all box backgrounds are black (invisible) — same SVG structure but no visible color, suitable as an animation intermediate. """ - group = [] - for li, line in enumerate(sloka.lines): - cells = [] + all_cells = [] + for line in sloka.lines: for vAkya in line.vAkyAni: for token in vAkya.tokens: if isinstance(token, str): @@ -238,13 +237,19 @@ def sloka_group_chandas(sloka: Sloka, chandas, blank: bool = False) -> Group: fill = ( "rgb(0, 0, 0, 0)" if blank else f'rgb("{bg.lstrip("#")}")' ) - cells.append( - f"box(fill: {fill}, inset: 6pt, radius: 3pt)" + all_cells.append( + f"box(fill: {fill}, width: 1.8em, height: 1.8em, radius: 0.4em)" f"[#align(center + horizon)[#text(fill: white)[{deva}]]]" ) - n = len(cells) - grid_code = f"#grid(columns: (auto,) * {n}, gutter: 3pt, {', '.join(cells)})" + pada_size = 8 + group = [] + for i in range(0, len(all_cells), pada_size): + pada_cells = all_cells[i : i + pada_size] + n = len(pada_cells) + grid_code = ( + f"#grid(columns: (auto,) * {n}, gutter: 3pt, {', '.join(pada_cells)})" + ) group.append(TypstText(set_font(grid_code, INTRO_FONT), scale=SCALE)) group = Group(*group) diff --git a/nirukta/timelines/introduce_sloka.py b/nirukta/timelines/introduce_sloka.py index d3e0cec..748e52d 100644 --- a/nirukta/timelines/introduce_sloka.py +++ b/nirukta/timelines/introduce_sloka.py @@ -44,23 +44,9 @@ def construct(self): chandas = _get_chandas() sloka_chandas_blank = sloka_group_chandas(self.sloka, chandas, blank=True) sloka_chandas = sloka_group_chandas(self.sloka, chandas) - self.play( - FlatAligned( - *( - LenientTransformMatchingDiff(sloka_g[i], sloka_chandas_blank[i], duration=0.6) - for i in range(len(sloka_g)) - ), - ) - ) + self.play(LenientTransformMatchingDiff(sloka_g, sloka_chandas_blank, duration=0.6)) # …then reveal the prosodic colors - self.play( - FlatAligned( - *( - LenientTransformMatchingDiff(sloka_chandas_blank[i], sloka_chandas[i], duration=0.6) - for i in range(len(sloka_g)) - ), - ) - ) + self.play(LenientTransformMatchingDiff(sloka_chandas_blank, sloka_chandas, duration=0.6)) self.play(Wait(2.0)) if self.citation is not None and self.citation != "sloka": From 8fd8d3a3460a8525be4fce1740723d1bcd0a9503 Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Thu, 21 May 2026 12:32:39 -0400 Subject: [PATCH 04/50] update layout for labels and meter --- nirukta/render.py | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/nirukta/render.py b/nirukta/render.py index 3014819..3e92973 100644 --- a/nirukta/render.py +++ b/nirukta/render.py @@ -243,18 +243,41 @@ def sloka_group_chandas(sloka: Sloka, chandas, blank: bool = False) -> Group: ) pada_size = 8 - group = [] + + # Build grid rows with no labels — so arrange(DOWN) centers the grid itself + rows = [] for i in range(0, len(all_cells), pada_size): pada_cells = all_cells[i : i + pada_size] n = len(pada_cells) - grid_code = ( - f"#grid(columns: (auto,) * {n}, gutter: 3pt, {', '.join(pada_cells)})" + grid_code = f"#grid(columns: (auto,) * {n}, gutter: 3pt, {', '.join(pada_cells)})" + rows.append(TypstText(set_font(grid_code, INTRO_FONT), scale=SCALE)) + + grid = Group(*rows) + grid.points.arrange(DOWN) + + if blank: + return grid + + # Position title and labels relative to the centered grid + meter_deva = transform_text("anuzwuB", Language.SANSKRIT) + title = TypstText( + set_font(f"#text(fill: white, size: 1.4em)[{meter_deva}]", INTRO_FONT), + scale=SCALE, + ) + title.points.next_to(grid, UP) + + pada_labels = [transform_text(str(n), Language.SANSKRIT) for n in range(1, 5)] + labels = [] + for pada_idx, row in enumerate(rows): + label_text = pada_labels[pada_idx] if pada_idx < len(pada_labels) else "" + label = TypstText( + set_font(f"#text(fill: white, size: 0.85em)[{label_text}]", INTRO_FONT), + scale=SCALE, ) - group.append(TypstText(set_font(grid_code, INTRO_FONT), scale=SCALE)) + label.points.next_to(row, LEFT) + labels.append(label) - group = Group(*group) - group.points.arrange(DOWN) - return group + return Group(title, *rows, *labels) def sloka_group_english(sloka: Sloka) -> Group[TypstText]: From fb402fe581a7cdecc4784a1a72c72bb2f32f48fd Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Sat, 23 May 2026 12:32:47 -0400 Subject: [PATCH 05/50] updating tester to show more things vidyut can do --- test.py | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 86 insertions(+), 9 deletions(-) diff --git a/test.py b/test.py index d06660f..70b37c5 100644 --- a/test.py +++ b/test.py @@ -1,7 +1,21 @@ import vidyut from pathlib import Path from vidyut.chandas import Chandas +from vidyut.cheda import Chedaka from vidyut.lipi import transliterate, Scheme +from vidyut.prakriya import ( + Dhatu, + Gana, + Lakara, + Linga, + Pada, + Pratipadika, + Purusha, + Prayoga, + Vacana, + Vibhakti, + Vyakarana, +) DATA_DIR = Path(__file__).parent / "vidyut_data" METERS_TSV = DATA_DIR / "chandas" / "meters.tsv" @@ -10,7 +24,14 @@ DATA_DIR.mkdir(exist_ok=True) vidyut.download_data(DATA_DIR) -# Lowercase so conventional caps don't confuse the IAST parser + +# ── 1. chandas ───────────────────────────────────────────────────────────────── +# Identify meter and classify syllable weights + +print("─" * 60) +print("1. CHANDAS — meter identification") +print("─" * 60) + padas_iast = [ "vakratuṇḍa mahākāya", "sūryakoṭi samaprabha", @@ -20,17 +41,73 @@ c = Chandas(str(METERS_TSV)) -# Sanity check: known example from the vidyut docs (expects vasantatilakā) _doc_match = c.classify("mAtaH samastajagatAM maDukEwaBAreH") -assert _doc_match.padya == "vasantatilakA", f"Library sanity check failed: got {_doc_match.padya}" +assert _doc_match.padya == "vasantatilakA", f"Sanity check failed: got {_doc_match.padya}" print("Sanity check passed: vasantatilakā identified correctly.\n") for i, iast in enumerate(padas_iast): slp1 = transliterate(iast, Scheme.Iast, Scheme.Slp1) - print(f"Pada {i + 1} (SLP1): {slp1}") match = c.classify(slp1) - print(f" Meter identified: {match.padya}") - for pada in match.aksharas: - for akshara in pada: - print(f" {akshara.text:12} {akshara.weight}") - print() + weights = " ".join( + akshara.weight + for pada in match.aksharas + for akshara in pada + ) + print(f"Pada {i + 1}: {iast}") + print(f" Meter : {match.padya}") + print(f" G/L : {weights}\n") + + +# ── 2. prakriya ──────────────────────────────────────────────────────────────── +# Derive word forms by applying Paninian grammar rules step-by-step. +# Using words from the sloka itself: kuru (do!) and deva (god). + +print("─" * 60) +print("2. PRAKRIYA — Paninian derivation") +print("─" * 60) + +v = Vyakarana() + +# kuru — imperative 2sg of kṛ (to do), the verb in "kuru me deva" +# aupadeshika (dhatupatha) form of kṛ is qukf\Y, tanadi (8th) gana +dhatu_kr = Dhatu.mula(r"qukf\Y", Gana.Tanadi) +pada_kuru = Pada.Tinanta(dhatu_kr, Prayoga.Kartari, Lakara.Lot, Purusha.Madhyama, Vacana.Eka) +results = v.derive(pada_kuru) +print(f"kṛ (to do) → imperative 2sg: {[r.text for r in results]}") +print("Derivation steps for 'kuru':") +kuru_result = next(r for r in results if r.text == "kuru") +for step in kuru_result.history: + print(f" [{step.code}] {' + '.join(step.result)}") + +print() + +# devaḥ — nominative singular masculine of deva (god) +prati_deva = Pratipadika.basic("deva") +pada_devah = Pada.Subanta(prati_deva, Linga.Pum, Vibhakti.Prathama, Vacana.Eka) +results = v.derive(pada_devah) +print(f"deva (god) → nominative sg masc: {[r.text for r in results]}") +print("Derivation steps for 'devaḥ':") +for step in results[0].history: + print(f" [{step.code}] {' + '.join(step.result)}") + +print() + + +# ── 3. cheda ─────────────────────────────────────────────────────────────────── +# Segment and morphologically tag a Sanskrit sentence. + +print("─" * 60) +print("3. CHEDA — word segmentation + morphological tagging") +print("─" * 60) + +chedaka = Chedaka(str(DATA_DIR)) + +# Use the third pada in SLP1 +slp1_pada = transliterate("nirvighnaṃ kuru me deva", Scheme.Iast, Scheme.Slp1) +print(f"Input (SLP1): {slp1_pada}\n") + +tokens = chedaka.run(slp1_pada) +for token in tokens: + iast_text = transliterate(token.text, Scheme.Slp1, Scheme.Iast) + iast_lemma = transliterate(token.lemma, Scheme.Slp1, Scheme.Iast) + print(f" {iast_text:16} lemma={iast_lemma:12} {token.data}") From ebb6de1fe0a4251a3a66253ee09b65bb106d4d4f Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Sun, 24 May 2026 17:05:30 -0400 Subject: [PATCH 06/50] reorganize around chandas in introduction --- nirukta/chandas.py | 13 ++ nirukta/render.py | 151 ----------------------- nirukta/sloka.py | 172 +++++++++++++++++++++++++++ nirukta/timelines/explain_sloka.py | 3 +- nirukta/timelines/introduce_sloka.py | 50 ++++---- nirukta/timelines/sloka_file.py | 2 +- nirukta/timelines/sutra_file.py | 4 +- 7 files changed, 220 insertions(+), 175 deletions(-) create mode 100644 nirukta/chandas.py create mode 100644 nirukta/sloka.py diff --git a/nirukta/chandas.py b/nirukta/chandas.py new file mode 100644 index 0000000..70bbf72 --- /dev/null +++ b/nirukta/chandas.py @@ -0,0 +1,13 @@ +from pathlib import Path + +import vidyut +from vidyut.chandas import Chandas + +_DATA_DIR = Path(__file__).parents[2] / "vidyut_data" +_METERS_TSV = _DATA_DIR / "chandas" / "meters.tsv" + + +if not _METERS_TSV.exists(): + _DATA_DIR.mkdir(exist_ok=True) + vidyut.download_data(_DATA_DIR) +chandas = Chandas(str(_METERS_TSV)) diff --git a/nirukta/render.py b/nirukta/render.py index 3e92973..6f7d06a 100644 --- a/nirukta/render.py +++ b/nirukta/render.py @@ -1,16 +1,10 @@ from janim.imports import ( C_LABEL_ANIM_DEFAULT, C_LABEL_ANIM_OUT, - BLUE_E, - RED_E, - LEFT, - MED_SMALL_BUFF, - UL, Aligned, Cmpt_Rgbas, DataUpdater, RateFunc, - Rect, SupportsAnim, AnimGroup, Succession, @@ -20,9 +14,6 @@ Group, GrowFromEdge, ShrinkToEdge, - SurroundingRect, - Text, - TypstText, VItem, ValueTracker, double_smooth, @@ -181,148 +172,6 @@ def mismatch(self): ) -def sloka_group(sloka: Sloka) -> Group[TypstText]: - group = [] - - for li, line in enumerate(sloka.lines): - sanskrit = "" - sanskritcode = "" - for vi, vAkya in enumerate(line.vAkyAni): - utterancetext = "" - for token in vAkya.tokens: - if isinstance(token, str): - sanskrit += token - utterancetext += token - else: - sanskrit += token.slp1 - utterancetext += token.slp1 - - sanskrit += " " - utterancetext += " " - utterance_code = f"{typst_code(utterancetext, Language.SANSKRIT)}" - sanskritcode += utterance_code + " " - - group.append( - TypstText( - # set_font(typst_code(sanskrit, Language.SANSKRIT), INTRO_FONT), - set_font(sanskritcode, INTRO_FONT), - scale=SCALE, - ) - ) - - group = Group(*group) - group.points.arrange(DOWN) - return group - - -def sloka_group_chandas(sloka: Sloka, chandas, blank: bool = False) -> Group: - """Each akshara in a grid cell; cell background encodes prosodic weight. - - Guru (heavy) → ORANGE background | Laghu (light) → TEAL background - Text is white throughout. One row per pada (hardcoded to 8 aksharas for anuṣṭubh). - When blank=True, all box backgrounds are black (invisible) — same SVG - structure but no visible color, suitable as an animation intermediate. - """ - all_cells = [] - for line in sloka.lines: - for vAkya in line.vAkyAni: - for token in vAkya.tokens: - if isinstance(token, str): - continue - match = chandas.classify(token.slp1) - for pada in match.aksharas: - for akshara in pada: - bg = BLUE_E if akshara.weight == "G" else RED_E - deva = transform_text(akshara.text, Language.SANSKRIT) - fill = ( - "rgb(0, 0, 0, 0)" if blank else f'rgb("{bg.lstrip("#")}")' - ) - all_cells.append( - f"box(fill: {fill}, width: 1.8em, height: 1.8em, radius: 0.4em)" - f"[#align(center + horizon)[#text(fill: white)[{deva}]]]" - ) - - pada_size = 8 - - # Build grid rows with no labels — so arrange(DOWN) centers the grid itself - rows = [] - for i in range(0, len(all_cells), pada_size): - pada_cells = all_cells[i : i + pada_size] - n = len(pada_cells) - grid_code = f"#grid(columns: (auto,) * {n}, gutter: 3pt, {', '.join(pada_cells)})" - rows.append(TypstText(set_font(grid_code, INTRO_FONT), scale=SCALE)) - - grid = Group(*rows) - grid.points.arrange(DOWN) - - if blank: - return grid - - # Position title and labels relative to the centered grid - meter_deva = transform_text("anuzwuB", Language.SANSKRIT) - title = TypstText( - set_font(f"#text(fill: white, size: 1.4em)[{meter_deva}]", INTRO_FONT), - scale=SCALE, - ) - title.points.next_to(grid, UP) - - pada_labels = [transform_text(str(n), Language.SANSKRIT) for n in range(1, 5)] - labels = [] - for pada_idx, row in enumerate(rows): - label_text = pada_labels[pada_idx] if pada_idx < len(pada_labels) else "" - label = TypstText( - set_font(f"#text(fill: white, size: 0.85em)[{label_text}]", INTRO_FONT), - scale=SCALE, - ) - label.points.next_to(row, LEFT) - labels.append(label) - - return Group(title, *rows, *labels) - - -def sloka_group_english(sloka: Sloka) -> Group[TypstText]: - group = [] - - for li, line in enumerate(sloka.lines): - english = "" - for vi, vAkya in enumerate(line.vAkyAni): - english += vAkya.english + "#linebreak()" - - group.append( - TypstText( - set_font(typst_code(english, Language.ENGLISH), LATIN_FONT), - scale=SCALE, - ) - ) - - group = Group(*group) - group.points.arrange(DOWN) - return group - - -def sloka_thumbnail(sloka: Sloka) -> Group: - sloka_text = sloka_group(sloka) - if sloka.number is not None: - number_label = Group( - Rect(0.4, 0.4, fill_alpha=0.3), - Text(f"{sloka.number}", font_size=22), - ) - number_label.points.next_to( - sloka_text, UP, buff=MED_SMALL_BUFF, aligned_edge=LEFT - ) - sloka_border = SurroundingRect( - Group(sloka_text, number_label), color=WHITE, buff=MED_SMALL_BUFF - ) - - group = scale_with_stroke(Group(sloka_text, sloka_border, number_label), 0.5) - else: - sloka_border = SurroundingRect(sloka_text, color=WHITE, buff=MED_SMALL_BUFF) - group = scale_with_stroke(Group(sloka_text, sloka_border), 0.5) - - group.points.to_border(UL, buff=MED_SMALL_BUFF) - return group - - def Junicode_translit(iast: str, color: str) -> str: """Like Junicode() but splits ṃ into m + combining dot for clean animation.""" if "ṃ" not in iast: diff --git a/nirukta/sloka.py b/nirukta/sloka.py new file mode 100644 index 0000000..4dcb863 --- /dev/null +++ b/nirukta/sloka.py @@ -0,0 +1,172 @@ +from janim.imports import ( + BLUE_E, + RED_E, + LEFT, + MED_SMALL_BUFF, + UL, + Rect, + DOWN, + UP, + Group, + SurroundingRect, + Text, + TypstText, +) +from nirukta.constants import INTRO_FONT, LATIN_FONT, SCALE +from nirukta.models import Language, Sloka +from janim.imports import WHITE + +from nirukta.render import scale_with_stroke, set_font, transform_text, typst_code +from nirukta.chandas import chandas + +_LONG_VOWELS_SLP1 = frozenset("AIUFXeEoO") + + +def sloka_group(sloka: Sloka) -> Group[TypstText]: + group = [] + + for li, line in enumerate(sloka.lines): + sanskrit = "" + sanskritcode = "" + for vi, vAkya in enumerate(line.vAkyAni): + utterancetext = "" + for token in vAkya.tokens: + if isinstance(token, str): + sanskrit += token + utterancetext += token + else: + sanskrit += token.slp1 + utterancetext += token.slp1 + + sanskrit += " " + utterancetext += " " + utterance_code = f"{typst_code(utterancetext, Language.SANSKRIT)}" + sanskritcode += utterance_code + " " + + group.append( + TypstText( + set_font(sanskritcode, INTRO_FONT), + scale=SCALE, + ) + ) + + group = Group(*group) + group.points.arrange(DOWN) + return group + + +def _is_long_vowel(slp1: str) -> bool: + """True if the SLP1 akshara contains a long vowel (dīrgha = 2 mātrās).""" + return any(c in _LONG_VOWELS_SLP1 for c in slp1) + + +def sloka_group_chandas( + sloka: Sloka, blank: bool = False, matras: bool = False +) -> Group: + base_width = 1.8 + + all_cells = [] + cell_idx = 0 + for line in sloka.lines: + for vAkya in line.vAkyAni: + for token in vAkya.tokens: + if isinstance(token, str): + continue + match = chandas.classify(token.slp1) + for pada in match.aksharas: + for akshara in pada: + bg = BLUE_E if akshara.weight == "G" else RED_E + deva = transform_text(akshara.text, Language.SANSKRIT) + fill = ( + "rgb(0, 0, 0, 0)" if blank else f'rgb("{bg.lstrip("#")}")' + ) + width = ( + f"{base_width * 2}em" + if (matras and _is_long_vowel(akshara.text)) + else f"{base_width}em" + ) + all_cells.append( + f"[#box(fill: {fill}, width: {width}, height: 1.8em, radius: 0.4em)" + f"[#align(center + horizon)[#text(fill: white)[{deva}]]]" + f" ]" + ) + cell_idx += 1 + + pada_size = 8 + + rows = [] + for i in range(0, len(all_cells), pada_size): + row_cells = all_cells[i : i + pada_size] + n = len(row_cells) + rows.append( + f"[#grid(columns: (auto,) * {n}, gutter: 0.5em, {', '.join(row_cells)})]" + ) + + grid_code = f"#grid(rows: (auto,) * {n}, gutter: 0.5em, {', '.join(rows)})" + grid = TypstText(set_font(grid_code, INTRO_FONT), scale=SCALE) + + return Group(grid) + # + # # Position title and labels relative to the centered grid + # meter_deva = transform_text("anuzwuB", Language.SANSKRIT) + # title = TypstText( + # set_font(f"#text(fill: white, size: 1.4em)[{meter_deva}]", INTRO_FONT), + # scale=SCALE, + # ) + # title.points.next_to(grid, UP) + # + # pada_labels = [transform_text(str(n), Language.SANSKRIT) for n in range(1, 5)] + # labels = [] + # for pada_idx, row in enumerate(rows): + # label_text = pada_labels[pada_idx] if pada_idx < len(pada_labels) else "" + # label = TypstText( + # set_font(f"#text(fill: white, size: 0.85em)[{label_text}]", INTRO_FONT), + # scale=SCALE, + # ) + # label.points.next_to(row, LEFT) + # labels.append(label) + # + # return Group(title, *rows, *labels) + + +def sloka_group_english(sloka: Sloka) -> Group[TypstText]: + group = [] + + for li, line in enumerate(sloka.lines): + english = "" + for vi, vAkya in enumerate(line.vAkyAni): + english += vAkya.english + "#linebreak()" + + group.append( + TypstText( + set_font(typst_code(english, Language.ENGLISH), LATIN_FONT), + scale=SCALE, + ) + ) + + group = Group(*group) + group.points.arrange(DOWN) + return group + + +def sloka_thumbnail(sloka: Sloka) -> Group: + sloka_text = sloka_group(sloka) + if sloka.number is not None: + number_label = Group( + Rect(0.4, 0.4, fill_alpha=0.3), + Text(f"{sloka.number}", font_size=22), + ) + number_label.points.next_to( + sloka_text, UP, buff=MED_SMALL_BUFF, aligned_edge=LEFT + ) + sloka_border = SurroundingRect( + Group(sloka_text, number_label), color=WHITE, buff=MED_SMALL_BUFF + ) + + group = scale_with_stroke(Group(sloka_text, sloka_border, number_label), 0.5) + else: + sloka_border = SurroundingRect(sloka_text, color=WHITE, buff=MED_SMALL_BUFF) + group = scale_with_stroke(Group(sloka_text, sloka_border), 0.5) + + group.points.to_border(UL, buff=MED_SMALL_BUFF) + return group diff --git a/nirukta/timelines/explain_sloka.py b/nirukta/timelines/explain_sloka.py index f84955a..8409b77 100644 --- a/nirukta/timelines/explain_sloka.py +++ b/nirukta/timelines/explain_sloka.py @@ -5,7 +5,8 @@ from janim.imports import YELLOW, Aligned, FadeIn, FadeOut, Succession, Timeline, Write from janim.logger import log from nirukta.models import Line, Sloka -from nirukta.render import Awaken, Sleep, sloka_group, sloka_thumbnail +from nirukta.render import Awaken, Sleep +from nirukta.sloka import sloka_group, sloka_thumbnail from nirukta.timelines import ( LenientTransformMatchingDiff, UtteranceTimeline, diff --git a/nirukta/timelines/introduce_sloka.py b/nirukta/timelines/introduce_sloka.py index 748e52d..4ae1bd1 100644 --- a/nirukta/timelines/introduce_sloka.py +++ b/nirukta/timelines/introduce_sloka.py @@ -1,25 +1,20 @@ -from pathlib import Path from typing import Optional -import vidyut from janim.imports import DOWN, YELLOW, FadeOut, Group, Timeline, TypstText, Wait, Write -from vidyut.chandas import Chandas from nirukta.constants import INTRO_FONT, SCALE from nirukta.models import Language, Sloka -from nirukta.render import FlatAligned, set_font, sloka_group, sloka_group_chandas, typst_code +from nirukta.render import ( + FlatAligned, + set_font, + typst_code, +) +from nirukta.sloka import ( + sloka_group, + sloka_group_chandas, +) from nirukta.timelines.transform import LenientTransformMatchingDiff -_DATA_DIR = Path(__file__).parents[2] / "vidyut_data" -_METERS_TSV = _DATA_DIR / "chandas" / "meters.tsv" - - -def _get_chandas() -> Chandas: - if not _METERS_TSV.exists(): - _DATA_DIR.mkdir(exist_ok=True) - vidyut.download_data(_DATA_DIR) - return Chandas(str(_METERS_TSV)) - class IntroduceSloka(Timeline): sloka: Sloka @@ -40,15 +35,28 @@ def construct(self): for line in sloka_g: self.play(Write(line, duration=4.0)) - # After full write-on, first move glyphs into grid boxes (no color)… - chandas = _get_chandas() - sloka_chandas_blank = sloka_group_chandas(self.sloka, chandas, blank=True) - sloka_chandas = sloka_group_chandas(self.sloka, chandas) - self.play(LenientTransformMatchingDiff(sloka_g, sloka_chandas_blank, duration=0.6)) - # …then reveal the prosodic colors - self.play(LenientTransformMatchingDiff(sloka_chandas_blank, sloka_chandas, duration=0.6)) + # Move glyphs into grid boxes + sloka_chandas_blank = sloka_group_chandas(self.sloka, blank=True) + sloka_chandas = sloka_group_chandas(self.sloka) + self.play( + LenientTransformMatchingDiff(sloka_g, sloka_chandas_blank, duration=0.6) + ) + + # Reveal the prosodic colors + self.play( + LenientTransformMatchingDiff( + sloka_chandas_blank, sloka_chandas, duration=0.6 + ) + ) self.play(Wait(2.0)) + # Expand boxes by vowel duration (dīrgha = 2×, hrasva = 1×) + sloka_matras = sloka_group_chandas(self.sloka, matras=True) + + self.play( + LenientTransformMatchingDiff(sloka_chandas, sloka_matras, duration=0.8) + ) + if self.citation is not None and self.citation != "sloka": citation_text = TypstText( set_font(typst_code(self.citation, Language.SANSKRIT), INTRO_FONT), diff --git a/nirukta/timelines/sloka_file.py b/nirukta/timelines/sloka_file.py index c11b4b1..0f62436 100644 --- a/nirukta/timelines/sloka_file.py +++ b/nirukta/timelines/sloka_file.py @@ -1,6 +1,6 @@ from janim.imports import ORANGE, FadeOut, Timeline, Wait, Write from nirukta.models import Sloka, SlokaFile -from nirukta.render import sloka_group_english +from nirukta.sloka import sloka_group_english from nirukta.timelines.explain_sloka import ExplainSloka, build_explain_sloka_cached from nirukta.timelines.introduce_sloka import IntroduceSloka diff --git a/nirukta/timelines/sutra_file.py b/nirukta/timelines/sutra_file.py index 1541070..24f8c33 100644 --- a/nirukta/timelines/sutra_file.py +++ b/nirukta/timelines/sutra_file.py @@ -30,9 +30,11 @@ Sleep, scale_with_stroke, set_font, + typst_code, +) +from nirukta.sloka import ( sloka_group_english, sloka_thumbnail, - typst_code, sloka_group, ) from nirukta.timelines.explain_sloka import ExplainSloka, build_explain_sloka_cached From 1b4b74b08082b8ef5ae2d92430b77149032a8db4 Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Sun, 24 May 2026 19:34:06 -0400 Subject: [PATCH 07/50] transform --- nirukta/chandas.py | 1 + nirukta/sloka.py | 12 ++++++--- nirukta/timelines/introduce_sloka.py | 40 +++++++++++++++++++--------- 3 files changed, 38 insertions(+), 15 deletions(-) diff --git a/nirukta/chandas.py b/nirukta/chandas.py index 70bbf72..d820a76 100644 --- a/nirukta/chandas.py +++ b/nirukta/chandas.py @@ -10,4 +10,5 @@ if not _METERS_TSV.exists(): _DATA_DIR.mkdir(exist_ok=True) vidyut.download_data(_DATA_DIR) + chandas = Chandas(str(_METERS_TSV)) diff --git a/nirukta/sloka.py b/nirukta/sloka.py index 4dcb863..f01e2d1 100644 --- a/nirukta/sloka.py +++ b/nirukta/sloka.py @@ -4,6 +4,7 @@ LEFT, MED_SMALL_BUFF, UL, + AnimGroup, Rect, DOWN, UP, @@ -18,6 +19,7 @@ from nirukta.render import scale_with_stroke, set_font, transform_text, typst_code from nirukta.chandas import chandas +from typing import Tuple, List _LONG_VOWELS_SLP1 = frozenset("AIUFXeEoO") @@ -62,11 +64,12 @@ def _is_long_vowel(slp1: str) -> bool: def sloka_group_chandas( sloka: Sloka, blank: bool = False, matras: bool = False -) -> Group: +) -> AnimGroup: base_width = 1.8 all_cells = [] cell_idx = 0 + cell_labels = [] for line in sloka.lines: for vAkya in line.vAkyAni: for token in vAkya.tokens: @@ -85,11 +88,13 @@ def sloka_group_chandas( if (matras and _is_long_vowel(akshara.text)) else f"{base_width}em" ) + cell_label = f"cell_{cell_idx}" all_cells.append( f"[#box(fill: {fill}, width: {width}, height: 1.8em, radius: 0.4em)" f"[#align(center + horizon)[#text(fill: white)[{deva}]]]" - f" ]" + f" <{cell_label}>]" ) + cell_labels.append(cell_label) cell_idx += 1 pada_size = 8 @@ -105,7 +110,8 @@ def sloka_group_chandas( grid_code = f"#grid(rows: (auto,) * {n}, gutter: 0.5em, {', '.join(rows)})" grid = TypstText(set_font(grid_code, INTRO_FONT), scale=SCALE) - return Group(grid) + # return Group(grid) + return grid # # # Position title and labels relative to the centered grid # meter_deva = transform_text("anuzwuB", Language.SANSKRIT) diff --git a/nirukta/timelines/introduce_sloka.py b/nirukta/timelines/introduce_sloka.py index 4ae1bd1..a1b3b5b 100644 --- a/nirukta/timelines/introduce_sloka.py +++ b/nirukta/timelines/introduce_sloka.py @@ -1,6 +1,17 @@ from typing import Optional -from janim.imports import DOWN, YELLOW, FadeOut, Group, Timeline, TypstText, Wait, Write +from janim.anims.transform import Transform +from janim.imports import ( + DOWN, + YELLOW, + FadeOut, + Group, + Timeline, + TypstText, + Wait, + Write, + Aligned, +) from nirukta.constants import INTRO_FONT, SCALE from nirukta.models import Language, Sloka @@ -38,24 +49,29 @@ def construct(self): # Move glyphs into grid boxes sloka_chandas_blank = sloka_group_chandas(self.sloka, blank=True) sloka_chandas = sloka_group_chandas(self.sloka) + self.play( LenientTransformMatchingDiff(sloka_g, sloka_chandas_blank, duration=0.6) ) # Reveal the prosodic colors - self.play( - LenientTransformMatchingDiff( - sloka_chandas_blank, sloka_chandas, duration=0.6 - ) - ) + self.play(Transform(sloka_chandas_blank, sloka_chandas, duration=0.6)) self.play(Wait(2.0)) - # Expand boxes by vowel duration (dīrgha = 2×, hrasva = 1×) + # Expand boxes by vowel duration sloka_matras = sloka_group_chandas(self.sloka, matras=True) - self.play( - LenientTransformMatchingDiff(sloka_chandas, sloka_matras, duration=0.8) - ) + self.play(Transform(sloka_chandas, sloka_matras, duration=0.8)) + self.play(Wait(2.0)) + # megatransform = [] + # for i in range(len(chandas_labels)): + # megatransform.append( + # Transform( + # sloka_chandas.get_label(chandas_labels[i]), + # sloka_matras.get_label(matras_labels[i]), + # duration=0.5, + # ) + # ) if self.citation is not None and self.citation != "sloka": citation_text = TypstText( @@ -67,8 +83,8 @@ def construct(self): for animation in [ Write(citation_text, duration=1.0), Wait(1.0), - FadeOut(Group(sloka_chandas, citation_text)), + FadeOut(Group(sloka_matras, citation_text)), ]: self.play(animation) else: - self.play(FadeOut(sloka_chandas)) + self.play(FadeOut(sloka_matras)) From f1bd98a28ded2c12dad9c88374209736e3d2fd88 Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Sun, 24 May 2026 21:22:15 -0400 Subject: [PATCH 08/50] thing1thing2 --- nirukta/sloka.py | 32 +++++++- nirukta/timelines/introduce_sloka.py | 38 ++++----- test.py | 117 ++++++++++++++------------- 3 files changed, 108 insertions(+), 79 deletions(-) diff --git a/nirukta/sloka.py b/nirukta/sloka.py index f01e2d1..0bd1abd 100644 --- a/nirukta/sloka.py +++ b/nirukta/sloka.py @@ -64,7 +64,7 @@ def _is_long_vowel(slp1: str) -> bool: def sloka_group_chandas( sloka: Sloka, blank: bool = False, matras: bool = False -) -> AnimGroup: +) -> tuple[TypstText, List[str]]: base_width = 1.8 all_cells = [] @@ -100,18 +100,21 @@ def sloka_group_chandas( pada_size = 8 rows = [] + row_labels = [] for i in range(0, len(all_cells), pada_size): row_cells = all_cells[i : i + pada_size] n = len(row_cells) + row_label = f"row_{i}" rows.append( - f"[#grid(columns: (auto,) * {n}, gutter: 0.5em, {', '.join(row_cells)})]" + f"[#box[#grid(columns: (auto,) * {n}, gutter: 0.5em, {', '.join(row_cells)})] <{row_label}>]" ) + row_labels.append(row_label) grid_code = f"#grid(rows: (auto,) * {n}, gutter: 0.5em, {', '.join(rows)})" grid = TypstText(set_font(grid_code, INTRO_FONT), scale=SCALE) # return Group(grid) - return grid + return (grid, row_labels) # # # Position title and labels relative to the centered grid # meter_deva = transform_text("anuzwuB", Language.SANSKRIT) @@ -135,6 +138,29 @@ def sloka_group_chandas( # return Group(title, *rows, *labels) +def title_and_pada_labels(texttttt: TypstText, labels: List[str]) -> Group: + # Position title and labels relative to the centered grid + meter_deva = transform_text("anuzwuB", Language.SANSKRIT) + title = TypstText( + set_font(f"#text(fill: white, size: 1.4em)[{meter_deva}]", INTRO_FONT), + scale=SCALE, + ) + title.points.next_to(texttttt, UP) + pada_labels = [transform_text(str(n), Language.SANSKRIT) for n in range(1, 5)] + labelz = [] + for pada_idx, c_label in enumerate(labels): + label_text = pada_labels[pada_idx] if pada_idx < len(pada_labels) else "" + label = TypstText( + set_font(f"#text(fill: white, size: 0.85em)[{label_text}]", INTRO_FONT), + scale=SCALE, + ) + print(texttttt.text) + label.points.next_to(texttttt.get_label(c_label), LEFT) + labelz.append(label) + + return Group(title, *labelz) + + def sloka_group_english(sloka: Sloka) -> Group[TypstText]: group = [] diff --git a/nirukta/timelines/introduce_sloka.py b/nirukta/timelines/introduce_sloka.py index a1b3b5b..681ee8d 100644 --- a/nirukta/timelines/introduce_sloka.py +++ b/nirukta/timelines/introduce_sloka.py @@ -2,7 +2,9 @@ from janim.anims.transform import Transform from janim.imports import ( + FadeIn, DOWN, + UP, YELLOW, FadeOut, Group, @@ -11,6 +13,7 @@ Wait, Write, Aligned, + LEFT, ) from nirukta.constants import INTRO_FONT, SCALE @@ -18,12 +21,10 @@ from nirukta.render import ( FlatAligned, set_font, + transform_text, typst_code, ) -from nirukta.sloka import ( - sloka_group, - sloka_group_chandas, -) +from nirukta.sloka import sloka_group, sloka_group_chandas, title_and_pada_labels from nirukta.timelines.transform import LenientTransformMatchingDiff @@ -47,8 +48,8 @@ def construct(self): self.play(Write(line, duration=4.0)) # Move glyphs into grid boxes - sloka_chandas_blank = sloka_group_chandas(self.sloka, blank=True) - sloka_chandas = sloka_group_chandas(self.sloka) + (sloka_chandas_blank, _) = sloka_group_chandas(self.sloka, blank=True) + (sloka_chandas, chandas_labels) = sloka_group_chandas(self.sloka) self.play( LenientTransformMatchingDiff(sloka_g, sloka_chandas_blank, duration=0.6) @@ -56,22 +57,23 @@ def construct(self): # Reveal the prosodic colors self.play(Transform(sloka_chandas_blank, sloka_chandas, duration=0.6)) - self.play(Wait(2.0)) + self.play(Wait(1.0)) + thing1 = title_and_pada_labels(sloka_chandas, chandas_labels) + self.play(FadeIn(thing1)) # Expand boxes by vowel duration - sloka_matras = sloka_group_chandas(self.sloka, matras=True) + (sloka_matras, matras_labels) = sloka_group_chandas(self.sloka, matras=True) + thing2 = title_and_pada_labels(sloka_matras, matras_labels) + + self.play( + Aligned( + Transform(sloka_chandas, sloka_matras), + Transform(thing1, thing2), + duration=0.8, + ) + ) - self.play(Transform(sloka_chandas, sloka_matras, duration=0.8)) self.play(Wait(2.0)) - # megatransform = [] - # for i in range(len(chandas_labels)): - # megatransform.append( - # Transform( - # sloka_chandas.get_label(chandas_labels[i]), - # sloka_matras.get_label(matras_labels[i]), - # duration=0.5, - # ) - # ) if self.citation is not None and self.citation != "sloka": citation_text = TypstText( diff --git a/test.py b/test.py index 70b37c5..b692191 100644 --- a/test.py +++ b/test.py @@ -2,18 +2,20 @@ from pathlib import Path from vidyut.chandas import Chandas from vidyut.cheda import Chedaka + +# from vidyut.kosha import Pada, Pratipadika from vidyut.lipi import transliterate, Scheme from vidyut.prakriya import ( Dhatu, - Gana, + # Gana, Lakara, Linga, - Pada, - Pratipadika, + # Pada, + # Pratipadika, Purusha, Prayoga, Vacana, - Vibhakti, + # Vibhakti, Vyakarana, ) @@ -32,82 +34,81 @@ print("1. CHANDAS — meter identification") print("─" * 60) -padas_iast = [ - "vakratuṇḍa mahākāya", - "sūryakoṭi samaprabha", - "nirvighnaṃ kuru me deva", - "sarva kāryeṣu sarvadā", +padas_slp1 = [ + "vakratuRqa mahAkAya", + "sUryakowi samapraBa", + "nirviGnaM kuru me deva", + "sarva kAryezu sarvadA", ] c = Chandas(str(METERS_TSV)) +chedaka = Chedaka(str(DATA_DIR)) _doc_match = c.classify("mAtaH samastajagatAM maDukEwaBAreH") -assert _doc_match.padya == "vasantatilakA", f"Sanity check failed: got {_doc_match.padya}" +assert _doc_match.padya == "vasantatilakA", ( + f"Sanity check failed: got {_doc_match.padya}" +) print("Sanity check passed: vasantatilakā identified correctly.\n") -for i, iast in enumerate(padas_iast): - slp1 = transliterate(iast, Scheme.Iast, Scheme.Slp1) +for i, slp1 in enumerate(padas_slp1): + iast = transliterate(slp1, Scheme.Slp1, Scheme.Iast) match = c.classify(slp1) - weights = " ".join( - akshara.weight - for pada in match.aksharas - for akshara in pada - ) - print(f"Pada {i + 1}: {iast}") + weights = " ".join(akshara.weight for pada in match.aksharas for akshara in pada) + print(f"Pada {i + 1}: {slp1}") print(f" Meter : {match.padya}") print(f" G/L : {weights}\n") + tokens = chedaka.run(slp1) + for token in tokens: + print(transliterate(token.lemma, Scheme.Slp1, Scheme.Iast)) + print(token) + print() # ── 2. prakriya ──────────────────────────────────────────────────────────────── # Derive word forms by applying Paninian grammar rules step-by-step. # Using words from the sloka itself: kuru (do!) and deva (god). -print("─" * 60) -print("2. PRAKRIYA — Paninian derivation") -print("─" * 60) - -v = Vyakarana() - -# kuru — imperative 2sg of kṛ (to do), the verb in "kuru me deva" -# aupadeshika (dhatupatha) form of kṛ is qukf\Y, tanadi (8th) gana -dhatu_kr = Dhatu.mula(r"qukf\Y", Gana.Tanadi) -pada_kuru = Pada.Tinanta(dhatu_kr, Prayoga.Kartari, Lakara.Lot, Purusha.Madhyama, Vacana.Eka) -results = v.derive(pada_kuru) -print(f"kṛ (to do) → imperative 2sg: {[r.text for r in results]}") -print("Derivation steps for 'kuru':") -kuru_result = next(r for r in results if r.text == "kuru") -for step in kuru_result.history: - print(f" [{step.code}] {' + '.join(step.result)}") - -print() - +# print("─" * 60) +# print("2. PRAKRIYA — Paninian derivation") +# print("─" * 60) +# +# v = Vyakarana() + +# # kuru — imperative 2sg of kṛ (to do), the verb in "kuru me deva" +# # aupadeshika (dhatupatha) form of kṛ is qukf\Y, tanadi (8th) gana +# dhatu_kr = Dhatu.mula(r"qukf\Y", Gana.Tanadi) +# pada_kuru = Pada.Tinanta( +# dhatu_kr, Prayoga.Kartari, Lakara.Lot, Purusha.Madhyama, Vacana.Eka +# ) +# results = v.derive(pada_kuru) +# print(f"kṛ (to do) → imperative 2sg: {[r.text for r in results]}") +# print("Derivation steps for 'kuru':") +# kuru_result = next(r for r in results if r.text == "kuru") +# for step in kuru_result.history: +# print(f" [{step.code}] {' + '.join(step.result)}") +# +# print() +# # devaḥ — nominative singular masculine of deva (god) -prati_deva = Pratipadika.basic("deva") -pada_devah = Pada.Subanta(prati_deva, Linga.Pum, Vibhakti.Prathama, Vacana.Eka) -results = v.derive(pada_devah) -print(f"deva (god) → nominative sg masc: {[r.text for r in results]}") -print("Derivation steps for 'devaḥ':") -for step in results[0].history: - print(f" [{step.code}] {' + '.join(step.result)}") - -print() +# prati_deva = Pratipadika.basic("deva") +# pada_devah = Pada.Subanta(prati_deva, Linga.Pum, Vibhakti.Prathama, Vacana.Eka) +# results = v.derive(pada_devah) +# print(f"deva (god) → nominative sg masc: {[r.text for r in results]}") +# print("Derivation steps for 'devaḥ':") +# for step in results[0].history: +# print(f" [{step.code}] {' + '.join(step.result)}") +# +# print() # ── 3. cheda ─────────────────────────────────────────────────────────────────── # Segment and morphologically tag a Sanskrit sentence. -print("─" * 60) -print("3. CHEDA — word segmentation + morphological tagging") -print("─" * 60) +# print("─" * 60) +# print("3. CHEDA — word segmentation + morphological tagging") +# print("─" * 60) -chedaka = Chedaka(str(DATA_DIR)) # Use the third pada in SLP1 -slp1_pada = transliterate("nirvighnaṃ kuru me deva", Scheme.Iast, Scheme.Slp1) -print(f"Input (SLP1): {slp1_pada}\n") - -tokens = chedaka.run(slp1_pada) -for token in tokens: - iast_text = transliterate(token.text, Scheme.Slp1, Scheme.Iast) - iast_lemma = transliterate(token.lemma, Scheme.Slp1, Scheme.Iast) - print(f" {iast_text:16} lemma={iast_lemma:12} {token.data}") +# slp1_pada = transliterate("nirvighnaṃ kuru me deva", Scheme.Iast, Scheme.Slp1) +# print(f"Input (SLP1): {slp1_pada}\n") From 8fdcbe21ff719d1232631c74880c532e4e6d49b9 Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Sun, 24 May 2026 21:41:17 -0400 Subject: [PATCH 09/50] cleaning? --- nirukta/sloka.py | 49 ++++++++++++---------------- nirukta/timelines/introduce_sloka.py | 30 +++++++++-------- 2 files changed, 37 insertions(+), 42 deletions(-) diff --git a/nirukta/sloka.py b/nirukta/sloka.py index 0bd1abd..8cc7209 100644 --- a/nirukta/sloka.py +++ b/nirukta/sloka.py @@ -1,3 +1,4 @@ +from attr import dataclass from janim.imports import ( BLUE_E, RED_E, @@ -62,10 +63,19 @@ def _is_long_vowel(slp1: str) -> bool: return any(c in _LONG_VOWELS_SLP1 for c in slp1) +@dataclass +class Keyed: + text: TypstText + keys: Group + + def sloka_group_chandas( - sloka: Sloka, blank: bool = False, matras: bool = False -) -> tuple[TypstText, List[str]]: + sloka: Sloka, + blank: bool = False, + matras: bool = False, +) -> Keyed: base_width = 1.8 + gutter = 1.8 all_cells = [] cell_idx = 0 @@ -90,7 +100,7 @@ def sloka_group_chandas( ) cell_label = f"cell_{cell_idx}" all_cells.append( - f"[#box(fill: {fill}, width: {width}, height: 1.8em, radius: 0.4em)" + f"[#box(fill: {fill}, width: {width}, height: {base_width}em, radius: 0.4em)" f"[#align(center + horizon)[#text(fill: white)[{deva}]]]" f" <{cell_label}>]" ) @@ -106,36 +116,18 @@ def sloka_group_chandas( n = len(row_cells) row_label = f"row_{i}" rows.append( - f"[#box[#grid(columns: (auto,) * {n}, gutter: 0.5em, {', '.join(row_cells)})] <{row_label}>]" + f"[#box[#grid(columns: (auto,) * {n}, gutter: {gutter}em, {', '.join(row_cells)})] <{row_label}>]" ) row_labels.append(row_label) - grid_code = f"#grid(rows: (auto,) * {n}, gutter: 0.5em, {', '.join(rows)})" + grid_code = f"#grid(rows: (auto,) * {n}, gutter: {gutter}em, {', '.join(rows)})" grid = TypstText(set_font(grid_code, INTRO_FONT), scale=SCALE) - # return Group(grid) - return (grid, row_labels) - # - # # Position title and labels relative to the centered grid - # meter_deva = transform_text("anuzwuB", Language.SANSKRIT) - # title = TypstText( - # set_font(f"#text(fill: white, size: 1.4em)[{meter_deva}]", INTRO_FONT), - # scale=SCALE, - # ) - # title.points.next_to(grid, UP) - # - # pada_labels = [transform_text(str(n), Language.SANSKRIT) for n in range(1, 5)] - # labels = [] - # for pada_idx, row in enumerate(rows): - # label_text = pada_labels[pada_idx] if pada_idx < len(pada_labels) else "" - # label = TypstText( - # set_font(f"#text(fill: white, size: 0.85em)[{label_text}]", INTRO_FONT), - # scale=SCALE, - # ) - # label.points.next_to(row, LEFT) - # labels.append(label) - # - # return Group(title, *rows, *labels) + if blank: + return Keyed(text=grid, keys=Group()) + + t = title_and_pada_labels(grid, row_labels) + return Keyed(text=grid, keys=t) def title_and_pada_labels(texttttt: TypstText, labels: List[str]) -> Group: @@ -154,7 +146,6 @@ def title_and_pada_labels(texttttt: TypstText, labels: List[str]) -> Group: set_font(f"#text(fill: white, size: 0.85em)[{label_text}]", INTRO_FONT), scale=SCALE, ) - print(texttttt.text) label.points.next_to(texttttt.get_label(c_label), LEFT) labelz.append(label) diff --git a/nirukta/timelines/introduce_sloka.py b/nirukta/timelines/introduce_sloka.py index 681ee8d..cd561e3 100644 --- a/nirukta/timelines/introduce_sloka.py +++ b/nirukta/timelines/introduce_sloka.py @@ -48,27 +48,29 @@ def construct(self): self.play(Write(line, duration=4.0)) # Move glyphs into grid boxes - (sloka_chandas_blank, _) = sloka_group_chandas(self.sloka, blank=True) - (sloka_chandas, chandas_labels) = sloka_group_chandas(self.sloka) + sloka_chandas_blank = sloka_group_chandas(self.sloka, blank=True) + sloka_chandas = sloka_group_chandas(self.sloka) self.play( - LenientTransformMatchingDiff(sloka_g, sloka_chandas_blank, duration=0.6) + LenientTransformMatchingDiff( + sloka_g, sloka_chandas_blank.text, duration=0.6 + ) ) # Reveal the prosodic colors - self.play(Transform(sloka_chandas_blank, sloka_chandas, duration=0.6)) + self.play(Transform(sloka_chandas_blank.text, sloka_chandas.text, duration=0.6)) self.play(Wait(1.0)) - thing1 = title_and_pada_labels(sloka_chandas, chandas_labels) - self.play(FadeIn(thing1)) + # Reveal keys + self.play(FadeIn(sloka_chandas.keys)) # Expand boxes by vowel duration - (sloka_matras, matras_labels) = sloka_group_chandas(self.sloka, matras=True) - thing2 = title_and_pada_labels(sloka_matras, matras_labels) + sloka_matras = sloka_group_chandas(self.sloka, matras=True) + # thing2 = title_and_pada_labels(sloka_matras, matras_labels) self.play( Aligned( - Transform(sloka_chandas, sloka_matras), - Transform(thing1, thing2), + Transform(sloka_chandas.text, sloka_matras.text), + Transform(sloka_chandas.keys, sloka_matras.keys), duration=0.8, ) ) @@ -81,12 +83,14 @@ def construct(self): scale=SCALE, ) print(citation_text.text) - citation_text.points.next_to(sloka_chandas, DOWN) + citation_text.points.next_to(sloka_chandas.text, DOWN) for animation in [ Write(citation_text, duration=1.0), Wait(1.0), - FadeOut(Group(sloka_matras, citation_text)), + FadeOut( + Group(Group(sloka_matras.text, sloka_matras.keys), citation_text) + ), ]: self.play(animation) else: - self.play(FadeOut(sloka_matras)) + self.play(FadeOut(Group(sloka_matras.text, sloka_matras.keys))) From e26ce7fc080429e104dc6253a9a7f0d8e089987d Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Sun, 24 May 2026 21:45:40 -0400 Subject: [PATCH 10/50] fix gutter --- nirukta/sloka.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nirukta/sloka.py b/nirukta/sloka.py index 8cc7209..34ef7ca 100644 --- a/nirukta/sloka.py +++ b/nirukta/sloka.py @@ -75,7 +75,7 @@ def sloka_group_chandas( matras: bool = False, ) -> Keyed: base_width = 1.8 - gutter = 1.8 + gutter = 0.5 all_cells = [] cell_idx = 0 From a8b09d2ba7e278d6f0959900bae1e44b88dbd2b1 Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Mon, 25 May 2026 00:33:17 -0400 Subject: [PATCH 11/50] cleanup --- nirukta/sloka.py | 8 ++++---- nirukta/timelines/introduce_sloka.py | 2 +- nirukta/timelines/sutra_file.py | 9 ++++++++- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/nirukta/sloka.py b/nirukta/sloka.py index 34ef7ca..5b16e7f 100644 --- a/nirukta/sloka.py +++ b/nirukta/sloka.py @@ -74,8 +74,8 @@ def sloka_group_chandas( blank: bool = False, matras: bool = False, ) -> Keyed: - base_width = 1.8 - gutter = 0.5 + base_width = 1.6 + gutter = 0.2 all_cells = [] cell_idx = 0 @@ -88,13 +88,13 @@ def sloka_group_chandas( match = chandas.classify(token.slp1) for pada in match.aksharas: for akshara in pada: - bg = BLUE_E if akshara.weight == "G" else RED_E + bg = BLUE_E if akshara.weight == "L" else RED_E deva = transform_text(akshara.text, Language.SANSKRIT) fill = ( "rgb(0, 0, 0, 0)" if blank else f'rgb("{bg.lstrip("#")}")' ) width = ( - f"{base_width * 2}em" + f"{base_width * 2 + gutter}em" if (matras and _is_long_vowel(akshara.text)) else f"{base_width}em" ) diff --git a/nirukta/timelines/introduce_sloka.py b/nirukta/timelines/introduce_sloka.py index cd561e3..7a97ac2 100644 --- a/nirukta/timelines/introduce_sloka.py +++ b/nirukta/timelines/introduce_sloka.py @@ -56,6 +56,7 @@ def construct(self): sloka_g, sloka_chandas_blank.text, duration=0.6 ) ) + self.play(Wait(1.0)) # Reveal the prosodic colors self.play(Transform(sloka_chandas_blank.text, sloka_chandas.text, duration=0.6)) @@ -65,7 +66,6 @@ def construct(self): # Expand boxes by vowel duration sloka_matras = sloka_group_chandas(self.sloka, matras=True) - # thing2 = title_and_pada_labels(sloka_matras, matras_labels) self.play( Aligned( diff --git a/nirukta/timelines/sutra_file.py b/nirukta/timelines/sutra_file.py index 24f8c33..22c2678 100644 --- a/nirukta/timelines/sutra_file.py +++ b/nirukta/timelines/sutra_file.py @@ -24,7 +24,11 @@ ) from nirukta.constants import INACTIVE, INTRO_FONT, SCALE from nirukta.models import Language, Sloka, SutraFile -from nirukta.timelines import LenientTransformMatchingDiff, UtteranceTimeline +from nirukta.timelines import ( + IntroduceSloka, + LenientTransformMatchingDiff, + UtteranceTimeline, +) from nirukta.render import ( Awaken, Sleep, @@ -74,6 +78,9 @@ def construct(self): self.play(animation) for sloka in self.slokas: + introduction = IntroduceSloka(sloka).build().to_item() + introduction.show() + self.forward_to(introduction.end) # explain = build_explain_sloka_cached(sloka).to_item().show() explain = ExplainSloka(sloka).build().to_item().show() self.forward_to(explain.end) From ed066f380ae57720223864cc15d6bc948b74fc72 Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Mon, 25 May 2026 03:38:54 -0400 Subject: [PATCH 12/50] scale using rectclip --- nirukta/timelines/sutra_file.py | 75 ++++++++++++++++++++++++++++++--- 1 file changed, 69 insertions(+), 6 deletions(-) diff --git a/nirukta/timelines/sutra_file.py b/nirukta/timelines/sutra_file.py index 22c2678..1b7cea1 100644 --- a/nirukta/timelines/sutra_file.py +++ b/nirukta/timelines/sutra_file.py @@ -6,14 +6,18 @@ MED_SMALL_BUFF, ORANGE, ORIGIN, + RIGHT, UL, UP, + UR, WHITE, Aligned, FadeIn, FadeOut, Group, Rect, + RectClip, + Succession, SurroundingRect, Text, Timeline, @@ -37,6 +41,7 @@ typst_code, ) from nirukta.sloka import ( + sloka_group_chandas, sloka_group_english, sloka_thumbnail, sloka_group, @@ -77,13 +82,71 @@ def construct(self): ]: self.play(animation) + # Listening side by side with pronunciation guide + + for sloka in self.slokas: - introduction = IntroduceSloka(sloka).build().to_item() - introduction.show() - self.forward_to(introduction.end) - # explain = build_explain_sloka_cached(sloka).to_item().show() - explain = ExplainSloka(sloka).build().to_item().show() - self.forward_to(explain.end) + # introduction = IntroduceSloka(sloka).build().to_item() + + # left = sloka_group(sloka) + # right = sloka_group(sloka) + # left.points.scale(0.5) + # right.points.scale(0.5) + # left.points.to_border(UL, buff=MED_SMALL_BUFF) + # right.points.to_border(UR, buff=MED_SMALL_BUFF) + + scaledown = 0.5 + + lt = sloka_group(sloka) + rt = sloka_group(sloka) + left = RectClip(lt, anchor=ORIGIN, border=False) + left.points.scale(scaledown) + left.transform.set(scale=scaledown) + right = RectClip(rt, anchor=ORIGIN, border=False) + right.transform.set(scale=scaledown) + right.points.scale(scaledown) + + + self.play( + + Succession( + FadeIn(Group(lt, left, rt, right)), + Wait(1.0), + Aligned( + left.anim.points.shift(LEFT * scaledown * 6), + right.anim.points.shift(RIGHT * scaledown * 6), + # right.anim.points.scale(0.2) + ), + Wait(1.0), + # FadeOut(Group(lt, left, rt, right)) + ) + ) + + blank = sloka_group_chandas(sloka, blank=True, matras=False) + chandas = sloka_group_chandas(sloka, blank=False, matras=False) + g = Group(blank.text, blank.keys, chandas.text, chandas.keys) + g.points.shift(RIGHT * 3) + + right.apply(*g) + # blank.text.points.scale(0.5) + # blank.text.points.to_border(UR, buff=MED_SMALL_BUFF) + + self.play(LenientTransformMatchingDiff(rt, blank.text)) + self.play(Transform(blank.text, chandas.text)) + self.play(Wait(1.0)) + self.play(FadeIn(chandas.keys)) + self.play(FadeOut(Group(lt, left, chandas.keys, chandas.text, right))) + + # group.points.scale(factor) + # scale_with_stroke(left, 0.5) + # scale_with_stroke(right, 0.5) + # introduction.show() + # self.forward_to(introduction.end) + + # for sloka in self.slokas: + # # explain = build_explain_sloka_cached(sloka).to_item().show() + # explain = ExplainSloka(sloka).build().to_item().show() + # self.forward_to(explain.end) # thumbnail = sloka_thumbnail(sloka) # initial = sloka_group(sloka) From e6ef45344bec9de9e4893c9e56df25214e0ecb5e Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Mon, 25 May 2026 06:29:27 -0400 Subject: [PATCH 13/50] duration --- nirukta/timelines/introduce_sloka.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nirukta/timelines/introduce_sloka.py b/nirukta/timelines/introduce_sloka.py index 7a97ac2..47af9c3 100644 --- a/nirukta/timelines/introduce_sloka.py +++ b/nirukta/timelines/introduce_sloka.py @@ -53,7 +53,7 @@ def construct(self): self.play( LenientTransformMatchingDiff( - sloka_g, sloka_chandas_blank.text, duration=0.6 + sloka_g, sloka_chandas_blank.text, duration=0.5 ) ) self.play(Wait(1.0)) @@ -71,7 +71,7 @@ def construct(self): Aligned( Transform(sloka_chandas.text, sloka_matras.text), Transform(sloka_chandas.keys, sloka_matras.keys), - duration=0.8, + duration=0.5, ) ) From 5f6928c5857df86dfe3cd36920c1df66e9e11685 Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Mon, 25 May 2026 08:35:06 -0400 Subject: [PATCH 14/50] skrutable --- nirukta/models/presentation/sloka.py | 42 ++++++++++++++++++ nirukta/sloka.py | 66 +++++++++++++++------------- nirukta/timelines/sutra_file.py | 12 ++++- pyproject.toml | 1 + uv.lock | 15 +++++++ 5 files changed, 104 insertions(+), 32 deletions(-) diff --git a/nirukta/models/presentation/sloka.py b/nirukta/models/presentation/sloka.py index 154d604..11c97d1 100644 --- a/nirukta/models/presentation/sloka.py +++ b/nirukta/models/presentation/sloka.py @@ -5,6 +5,8 @@ from nirukta.models.presentation.line import Line +from skrutable.meter_identification import MeterIdentifier +MI = MeterIdentifier() @dataclass class Sloka: @@ -25,3 +27,43 @@ def __init__(self, lines: List[Line]) -> None: self.number = number pass + + def slp1(self): + slp1 = "" + for line in self.lines: + for vAkya in line.vAkyAni: + for token in vAkya.tokens: + if isinstance(token, str): + slp1 += token + else: + slp1 += token.slp1 + " " + + slp1 += "\n" + return slp1 + + def meter(self): + verse = MI.identify_meter(self.slp1(), from_scheme='SLP') + # print(list(map(lambda x: x.split(''), ))) + weight_lines = verse.syllable_weights.split('\n') + text_lines = verse.text_syllabified.split('\n') + + tups = [] + padas = [] + for li in range(len(text_lines)): + line_sounds = list(filter(lambda x: len(x) > 0, text_lines[li].split(' '))) + print(line_sounds) + print(weight_lines[li]) + for si in range(len(line_sounds)): + tups.append((line_sounds[si], weight_lines[li][si])) + + padas.append(tups) + tups = [] + + # list(map(lambda x: x.split(' '), text_lines)) + # + # print(verse.text_syllabified.split('\n')) + # + print(verse.meter_label) + print(padas) + return ((verse.meter_label, padas)) + # print(verse.summarize()) diff --git a/nirukta/sloka.py b/nirukta/sloka.py index 5b16e7f..0c94e78 100644 --- a/nirukta/sloka.py +++ b/nirukta/sloka.py @@ -80,34 +80,38 @@ def sloka_group_chandas( all_cells = [] cell_idx = 0 cell_labels = [] - for line in sloka.lines: - for vAkya in line.vAkyAni: - for token in vAkya.tokens: - if isinstance(token, str): - continue - match = chandas.classify(token.slp1) - for pada in match.aksharas: - for akshara in pada: - bg = BLUE_E if akshara.weight == "L" else RED_E - deva = transform_text(akshara.text, Language.SANSKRIT) - fill = ( - "rgb(0, 0, 0, 0)" if blank else f'rgb("{bg.lstrip("#")}")' - ) - width = ( - f"{base_width * 2 + gutter}em" - if (matras and _is_long_vowel(akshara.text)) - else f"{base_width}em" - ) - cell_label = f"cell_{cell_idx}" - all_cells.append( - f"[#box(fill: {fill}, width: {width}, height: {base_width}em, radius: 0.4em)" - f"[#align(center + horizon)[#text(fill: white)[{deva}]]]" - f" <{cell_label}>]" - ) - cell_labels.append(cell_label) - cell_idx += 1 - - pada_size = 8 + + (meter_label, padas) = sloka.meter() + + + # for line in sloka.lines: + # for vAkya in line.vAkyAni: + # for token in vAkya.tokens: + # if isinstance(token, str): + # continue + # match = chandas.classify(token.slp1) + for pada in padas: + for (akt, akw) in pada: + bg = BLUE_E if akw == "l" else RED_E + deva = transform_text(akt, Language.SANSKRIT) + fill = ( + "rgb(0, 0, 0, 0)" if blank else f'rgb("{bg.lstrip("#")}")' + ) + width = ( + f"{base_width * 2 + gutter}em" + if (matras and _is_long_vowel(akt)) + else f"{base_width}em" + ) + cell_label = f"cell_{cell_idx}" + all_cells.append( + f"[#box(fill: {fill}, width: {width}, height: {base_width}em, radius: 0.4em)" + f"[#align(center + horizon)[#text(fill: white)[{deva}]]]" + f" <{cell_label}>]" + ) + cell_labels.append(cell_label) + cell_idx += 1 + + pada_size = len(padas[0]) rows = [] row_labels = [] @@ -126,13 +130,13 @@ def sloka_group_chandas( if blank: return Keyed(text=grid, keys=Group()) - t = title_and_pada_labels(grid, row_labels) + t = title_and_pada_labels(meter_label, grid, row_labels) return Keyed(text=grid, keys=t) -def title_and_pada_labels(texttttt: TypstText, labels: List[str]) -> Group: +def title_and_pada_labels(meter_label: str, texttttt: TypstText, labels: List[str]) -> Group: # Position title and labels relative to the centered grid - meter_deva = transform_text("anuzwuB", Language.SANSKRIT) + meter_deva = transform_text(meter_label, Language.SANSKRIT) title = TypstText( set_font(f"#text(fill: white, size: 1.4em)[{meter_deva}]", INTRO_FONT), scale=SCALE, diff --git a/nirukta/timelines/sutra_file.py b/nirukta/timelines/sutra_file.py index 1b7cea1..97a9ef8 100644 --- a/nirukta/timelines/sutra_file.py +++ b/nirukta/timelines/sutra_file.py @@ -83,6 +83,16 @@ def construct(self): self.play(animation) # Listening side by side with pronunciation guide + for sloka in self.slokas: + sloka.meter() + # slp1 = sloka.slp1() + + # sloka. + # verse = MI.identify_meter(slp1) # from_scheme auto-detected; output IAST + # print(verse.meter_label) + # print(verse.summarize()) + # verse = MI.identify_meter(slp1, resplit_option='none') + # verse = MI.identify_meter(slp1, from_scheme='SLP', resplit_option='resplit_lite') for sloka in self.slokas: @@ -125,7 +135,7 @@ def construct(self): blank = sloka_group_chandas(sloka, blank=True, matras=False) chandas = sloka_group_chandas(sloka, blank=False, matras=False) g = Group(blank.text, blank.keys, chandas.text, chandas.keys) - g.points.shift(RIGHT * 3) + # g.points.shift(RIGHT * 3) right.apply(*g) # blank.text.points.scale(0.5) diff --git a/pyproject.toml b/pyproject.toml index d91bd95..6ac92ef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,6 +9,7 @@ dependencies = [ "dill>=0.4.1", "janim[gui]>=4.1.0", "parsimonious>=0.11.0", + "skrutable>=2.6.3", "vidyut>=0.4.0", ] diff --git a/uv.lock b/uv.lock index 02e6df8..af86125 100644 --- a/uv.lock +++ b/uv.lock @@ -450,6 +450,7 @@ dependencies = [ { name = "dill" }, { name = "janim", extra = ["gui"] }, { name = "parsimonious" }, + { name = "skrutable" }, { name = "vidyut" }, ] @@ -459,6 +460,7 @@ requires-dist = [ { name = "dill", specifier = ">=0.4.1" }, { name = "janim", extras = ["gui"], specifier = ">=4.1.0" }, { name = "parsimonious", specifier = ">=0.11.0" }, + { name = "skrutable", specifier = ">=2.6.3" }, { name = "vidyut", specifier = ">=0.4.0" }, ] @@ -873,6 +875,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bf/d1/698e85d5f7e2ce3b731232dc9f26e2cecb8afc66194aa494dc78c04194cc/skia_pathops-0.9.1-cp310-abi3-win_amd64.whl", hash = "sha256:e718f2e1284f05ccccde111b1280a79e33093c4af30c118a0b2f5b0753f0727e", size = 1782888, upload-time = "2025-12-08T11:44:39.983Z" }, ] +[[package]] +name = "skrutable" +version = "2.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7f/04/d51ce33b748e808bf0e0a8b19033ebda123856fb0b87707d81f10ac0e280/skrutable-2.6.3.tar.gz", hash = "sha256:b457de2361bf4dbfce1bc036747834444406f677d94c9f5cde6c9a936c1a1a68", size = 71677, upload-time = "2026-05-16T01:58:04.415Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/6b/c6f28fcaa48df66df378e110be1145fcc708d82925bdd79104c406cc9a03/skrutable-2.6.3-py3-none-any.whl", hash = "sha256:4bbcbe16809aa693cfc4cfe34c43f321a3bdd4089c7bd499f1f8714e1847cfa6", size = 73253, upload-time = "2026-05-16T01:58:03.184Z" }, +] + [[package]] name = "sounddevice" version = "0.5.5" From a5929fa240fa1c8016e15ff237801673fce68df8 Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Mon, 25 May 2026 08:44:38 -0400 Subject: [PATCH 15/50] meow --- nirukta/models/presentation/sloka.py | 20 ++++++++++++++------ nirukta/sloka.py | 23 +++++++++++------------ 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/nirukta/models/presentation/sloka.py b/nirukta/models/presentation/sloka.py index 11c97d1..f890906 100644 --- a/nirukta/models/presentation/sloka.py +++ b/nirukta/models/presentation/sloka.py @@ -1,4 +1,5 @@ from dataclasses import dataclass +from aksharamukha import transliterate from typing import List, Optional from nirukta.constants import DIGITS_RE @@ -6,8 +7,10 @@ from nirukta.models.presentation.line import Line from skrutable.meter_identification import MeterIdentifier + MI = MeterIdentifier() + @dataclass class Sloka: """""" @@ -41,16 +44,16 @@ def slp1(self): slp1 += "\n" return slp1 - def meter(self): - verse = MI.identify_meter(self.slp1(), from_scheme='SLP') + def meter(self) -> tuple[str, List[tuple(str, str)]]: + verse = MI.identify_meter(self.slp1(), from_scheme="SLP") # print(list(map(lambda x: x.split(''), ))) - weight_lines = verse.syllable_weights.split('\n') - text_lines = verse.text_syllabified.split('\n') + weight_lines = verse.syllable_weights.split("\n") + text_lines = verse.text_syllabified.split("\n") tups = [] padas = [] for li in range(len(text_lines)): - line_sounds = list(filter(lambda x: len(x) > 0, text_lines[li].split(' '))) + line_sounds = list(filter(lambda x: len(x) > 0, text_lines[li].split(" "))) print(line_sounds) print(weight_lines[li]) for si in range(len(line_sounds)): @@ -64,6 +67,11 @@ def meter(self): # print(verse.text_syllabified.split('\n')) # print(verse.meter_label) + print(verse.morae_per_line) print(padas) - return ((verse.meter_label, padas)) + + label = transliterate.process("IAST", "SLP1", verse.meter_label) + assert label is not None + + return (label, padas) # print(verse.summarize()) diff --git a/nirukta/sloka.py b/nirukta/sloka.py index 0c94e78..011ea79 100644 --- a/nirukta/sloka.py +++ b/nirukta/sloka.py @@ -83,20 +83,17 @@ def sloka_group_chandas( (meter_label, padas) = sloka.meter() - # for line in sloka.lines: - # for vAkya in line.vAkyAni: - # for token in vAkya.tokens: - # if isinstance(token, str): - # continue - # match = chandas.classify(token.slp1) + # for vAkya in line.vAkyAni: + # for token in vAkya.tokens: + # if isinstance(token, str): + # continue + # match = chandas.classify(token.slp1) for pada in padas: - for (akt, akw) in pada: - bg = BLUE_E if akw == "l" else RED_E + for akt, akw in pada: + bg = BLUE_E if akw == "g" else RED_E deva = transform_text(akt, Language.SANSKRIT) - fill = ( - "rgb(0, 0, 0, 0)" if blank else f'rgb("{bg.lstrip("#")}")' - ) + fill = "rgb(0, 0, 0, 0)" if blank else f'rgb("{bg.lstrip("#")}")' width = ( f"{base_width * 2 + gutter}em" if (matras and _is_long_vowel(akt)) @@ -134,7 +131,9 @@ def sloka_group_chandas( return Keyed(text=grid, keys=t) -def title_and_pada_labels(meter_label: str, texttttt: TypstText, labels: List[str]) -> Group: +def title_and_pada_labels( + meter_label: str, texttttt: TypstText, labels: List[str] +) -> Group: # Position title and labels relative to the centered grid meter_deva = transform_text(meter_label, Language.SANSKRIT) title = TypstText( From afda8815e64a85500bfc4129c7e0f1cd039e8894 Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Mon, 25 May 2026 09:05:45 -0400 Subject: [PATCH 16/50] reorganize --- nirukta/models/presentation/akshara.py | 20 ++++++++++++++++++++ nirukta/models/presentation/sloka.py | 24 ++++++++---------------- nirukta/sloka.py | 17 +++++------------ 3 files changed, 33 insertions(+), 28 deletions(-) create mode 100644 nirukta/models/presentation/akshara.py diff --git a/nirukta/models/presentation/akshara.py b/nirukta/models/presentation/akshara.py new file mode 100644 index 0000000..e213950 --- /dev/null +++ b/nirukta/models/presentation/akshara.py @@ -0,0 +1,20 @@ +from attr import dataclass +from skrutable.meter_identification import MeterIdentifier + +MI = MeterIdentifier() + + +_LONG_VOWELS_SLP1 = frozenset("AIUFXeEoO") + + +@dataclass +class Akshara: + text: str + weight: str + + def is_long(self): + """True if the SLP1 akshara contains a long vowel.""" + return any(c in _LONG_VOWELS_SLP1 for c in self.text) + + +identify = MI.identify_meter diff --git a/nirukta/models/presentation/sloka.py b/nirukta/models/presentation/sloka.py index f890906..ddfad6b 100644 --- a/nirukta/models/presentation/sloka.py +++ b/nirukta/models/presentation/sloka.py @@ -4,12 +4,9 @@ from nirukta.constants import DIGITS_RE +from nirukta.models.presentation.akshara import Akshara, identify from nirukta.models.presentation.line import Line -from skrutable.meter_identification import MeterIdentifier - -MI = MeterIdentifier() - @dataclass class Sloka: @@ -44,28 +41,24 @@ def slp1(self): slp1 += "\n" return slp1 - def meter(self) -> tuple[str, List[tuple(str, str)]]: - verse = MI.identify_meter(self.slp1(), from_scheme="SLP") + def meter(self) -> tuple[str, List[Akshara]]: + verse = identify(self.slp1(), from_scheme="SLP") # print(list(map(lambda x: x.split(''), ))) weight_lines = verse.syllable_weights.split("\n") text_lines = verse.text_syllabified.split("\n") - tups = [] padas = [] for li in range(len(text_lines)): line_sounds = list(filter(lambda x: len(x) > 0, text_lines[li].split(" "))) print(line_sounds) print(weight_lines[li]) + aksharas = [] for si in range(len(line_sounds)): - tups.append((line_sounds[si], weight_lines[li][si])) - - padas.append(tups) - tups = [] + aksharas.append( + Akshara(text=line_sounds[si], weight=weight_lines[li][si]) + ) + padas.append(aksharas) - # list(map(lambda x: x.split(' '), text_lines)) - # - # print(verse.text_syllabified.split('\n')) - # print(verse.meter_label) print(verse.morae_per_line) print(padas) @@ -74,4 +67,3 @@ def meter(self) -> tuple[str, List[tuple(str, str)]]: assert label is not None return (label, padas) - # print(verse.summarize()) diff --git a/nirukta/sloka.py b/nirukta/sloka.py index 011ea79..2eff900 100644 --- a/nirukta/sloka.py +++ b/nirukta/sloka.py @@ -20,9 +20,7 @@ from nirukta.render import scale_with_stroke, set_font, transform_text, typst_code from nirukta.chandas import chandas -from typing import Tuple, List - -_LONG_VOWELS_SLP1 = frozenset("AIUFXeEoO") +from typing import List def sloka_group(sloka: Sloka) -> Group[TypstText]: @@ -58,11 +56,6 @@ def sloka_group(sloka: Sloka) -> Group[TypstText]: return group -def _is_long_vowel(slp1: str) -> bool: - """True if the SLP1 akshara contains a long vowel (dīrgha = 2 mātrās).""" - return any(c in _LONG_VOWELS_SLP1 for c in slp1) - - @dataclass class Keyed: text: TypstText @@ -90,13 +83,13 @@ def sloka_group_chandas( # continue # match = chandas.classify(token.slp1) for pada in padas: - for akt, akw in pada: - bg = BLUE_E if akw == "g" else RED_E - deva = transform_text(akt, Language.SANSKRIT) + for akshara in pada: + bg = BLUE_E if akshara.weight == "g" else RED_E + deva = transform_text(akshara.text, Language.SANSKRIT) fill = "rgb(0, 0, 0, 0)" if blank else f'rgb("{bg.lstrip("#")}")' width = ( f"{base_width * 2 + gutter}em" - if (matras and _is_long_vowel(akt)) + if (matras and akshara.is_long()) else f"{base_width}em" ) cell_label = f"cell_{cell_idx}" From 28adf8f276ce2998d53d0e2dfa034869c2837feb Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Mon, 25 May 2026 12:27:42 -0400 Subject: [PATCH 17/50] corner trickery --- nirukta/constants.py | 1 - nirukta/models/presentation/akshara.py | 5 + nirukta/models/presentation/sloka.py | 7 +- nirukta/render.py | 2 +- nirukta/sloka.py | 102 ++++++++++------ nirukta/timelines/explain_sloka.py | 2 +- nirukta/timelines/introduce_sloka.py | 12 +- nirukta/timelines/recitation.py | 79 +++++++++++++ nirukta/timelines/sutra_file.py | 158 +++++++++++++++---------- nirukta/typst.py | 40 +++++++ 10 files changed, 299 insertions(+), 109 deletions(-) create mode 100644 nirukta/timelines/recitation.py create mode 100644 nirukta/typst.py diff --git a/nirukta/constants.py b/nirukta/constants.py index 24453fd..691b04f 100644 --- a/nirukta/constants.py +++ b/nirukta/constants.py @@ -13,7 +13,6 @@ ) SCALE = 1.3 -INTRO_FONT = "Tiro Devanagari Sanskrit" SANSKRIT_FONT = "Tiro Devanagari Sanskrit" LATIN_FONT = "Junicode" diff --git a/nirukta/models/presentation/akshara.py b/nirukta/models/presentation/akshara.py index e213950..f30a487 100644 --- a/nirukta/models/presentation/akshara.py +++ b/nirukta/models/presentation/akshara.py @@ -1,4 +1,5 @@ from attr import dataclass +from janim.imports import BLUE_E, RED_E from skrutable.meter_identification import MeterIdentifier MI = MeterIdentifier() @@ -16,5 +17,9 @@ def is_long(self): """True if the SLP1 akshara contains a long vowel.""" return any(c in _LONG_VOWELS_SLP1 for c in self.text) + def rgb_color(self): + bg = BLUE_E if self.weight == "g" else RED_E + return f'rgb("{bg.lstrip("#")}")' + identify = MI.identify_meter diff --git a/nirukta/models/presentation/sloka.py b/nirukta/models/presentation/sloka.py index ddfad6b..036ba30 100644 --- a/nirukta/models/presentation/sloka.py +++ b/nirukta/models/presentation/sloka.py @@ -41,18 +41,19 @@ def slp1(self): slp1 += "\n" return slp1 - def meter(self) -> tuple[str, List[Akshara]]: + def meter(self) -> tuple[str, List[List[Akshara]]]: verse = identify(self.slp1(), from_scheme="SLP") # print(list(map(lambda x: x.split(''), ))) weight_lines = verse.syllable_weights.split("\n") text_lines = verse.text_syllabified.split("\n") - padas = [] + padas: List[List[Akshara]] = [] for li in range(len(text_lines)): line_sounds = list(filter(lambda x: len(x) > 0, text_lines[li].split(" "))) print(line_sounds) print(weight_lines[li]) - aksharas = [] + + aksharas: List[Akshara] = [] for si in range(len(line_sounds)): aksharas.append( Akshara(text=line_sounds[si], weight=weight_lines[li][si]) diff --git a/nirukta/render.py b/nirukta/render.py index 6f7d06a..806fa7a 100644 --- a/nirukta/render.py +++ b/nirukta/render.py @@ -22,7 +22,7 @@ smooth, there_and_back, ) -from nirukta.constants import INACTIVE, INTRO_FONT, LATIN_FONT, SCALE, TYPST_CMD_RE +from nirukta.constants import INACTIVE, SCALE, TYPST_CMD_RE from nirukta.models import Language, Sloka from janim.imports import WHITE, C_LABEL_ANIM_ABSTRACT from aksharamukha import transliterate diff --git a/nirukta/sloka.py b/nirukta/sloka.py index 2eff900..09c9001 100644 --- a/nirukta/sloka.py +++ b/nirukta/sloka.py @@ -14,7 +14,7 @@ Text, TypstText, ) -from nirukta.constants import INTRO_FONT, LATIN_FONT, SCALE +from nirukta.constants import SANSKRIT_FONT, LATIN_FONT, SCALE from nirukta.models import Language, Sloka from janim.imports import WHITE @@ -22,8 +22,10 @@ from nirukta.chandas import chandas from typing import List +from nirukta.typst import arrange_vertical, box_cell, arrange_horizontal -def sloka_group(sloka: Sloka) -> Group[TypstText]: + +""" def sloka_group(sloka: Sloka) -> Group[TypstText]: group = [] for li, line in enumerate(sloka.lines): @@ -46,7 +48,7 @@ def sloka_group(sloka: Sloka) -> Group[TypstText]: group.append( TypstText( - set_font(sanskritcode, INTRO_FONT), + set_font(sanskritcode, SANSKRIT_FONT), scale=SCALE, ) ) @@ -54,6 +56,45 @@ def sloka_group(sloka: Sloka) -> Group[TypstText]: group = Group(*group) group.points.arrange(DOWN) return group +""" + + +def sloka_group_reformed(sloka: Sloka, devanagari: bool = True) -> TypstText: + rows = [] + + if devanagari: + lang = Language.SANSKRIT + font = SANSKRIT_FONT + else: + lang = Language.TRANSLIT + font = LATIN_FONT + + for li, line in enumerate(sloka.lines): + sanskritcode = "" + for vi, vAkya in enumerate(line.vAkyAni): + utterancetext = "" + for token in vAkya.tokens: + if isinstance(token, str): + utterancetext += token + else: + utterancetext += token.slp1 + + utterancetext += " " + utterance_code = ( + f"{typst_code(utterancetext, lang)}" + ) + sanskritcode += utterance_code + " " + + sanskritcode = f"[{sanskritcode}]" + rows.append(sanskritcode) + + grid = arrange_vertical(rows, gutter=0.6) + print(f"\n\ngrid:\n{grid}\n") + + return TypstText( + set_font(grid, font), + scale=SCALE, + ) @dataclass @@ -72,50 +113,41 @@ def sloka_group_chandas( all_cells = [] cell_idx = 0 - cell_labels = [] (meter_label, padas) = sloka.meter() - # for line in sloka.lines: - # for vAkya in line.vAkyAni: - # for token in vAkya.tokens: - # if isinstance(token, str): - # continue - # match = chandas.classify(token.slp1) for pada in padas: for akshara in pada: - bg = BLUE_E if akshara.weight == "g" else RED_E deva = transform_text(akshara.text, Language.SANSKRIT) - fill = "rgb(0, 0, 0, 0)" if blank else f'rgb("{bg.lstrip("#")}")' - width = ( - f"{base_width * 2 + gutter}em" - if (matras and akshara.is_long()) - else f"{base_width}em" - ) - cell_label = f"cell_{cell_idx}" + fill = None if blank else akshara.rgb_color() all_cells.append( - f"[#box(fill: {fill}, width: {width}, height: {base_width}em, radius: 0.4em)" - f"[#align(center + horizon)[#text(fill: white)[{deva}]]]" - f" <{cell_label}>]" + box_cell( + content=deva, + width=( + base_width * 2 + gutter + if (matras and akshara.is_long()) + else base_width + ), + idx=cell_idx, + fill=fill, + ) ) - cell_labels.append(cell_label) cell_idx += 1 - pada_size = len(padas[0]) - rows = [] row_labels = [] - for i in range(0, len(all_cells), pada_size): - row_cells = all_cells[i : i + pada_size] - n = len(row_cells) - row_label = f"row_{i}" - rows.append( - f"[#box[#grid(columns: (auto,) * {n}, gutter: {gutter}em, {', '.join(row_cells)})] <{row_label}>]" - ) + + i = 0 + for idx, pada in enumerate(padas): + n = len(pada) + row_cells = all_cells[i : i + n] + rows.append(arrange_horizontal(row_cells, idx)) + i += n + row_label = f"row_{idx}" row_labels.append(row_label) grid_code = f"#grid(rows: (auto,) * {n}, gutter: {gutter}em, {', '.join(rows)})" - grid = TypstText(set_font(grid_code, INTRO_FONT), scale=SCALE) + grid = TypstText(set_font(grid_code, SANSKRIT_FONT), scale=SCALE) if blank: return Keyed(text=grid, keys=Group()) @@ -130,7 +162,7 @@ def title_and_pada_labels( # Position title and labels relative to the centered grid meter_deva = transform_text(meter_label, Language.SANSKRIT) title = TypstText( - set_font(f"#text(fill: white, size: 1.4em)[{meter_deva}]", INTRO_FONT), + set_font(f"#text(fill: white, size: 1.4em)[{meter_deva}]", SANSKRIT_FONT), scale=SCALE, ) title.points.next_to(texttttt, UP) @@ -139,7 +171,7 @@ def title_and_pada_labels( for pada_idx, c_label in enumerate(labels): label_text = pada_labels[pada_idx] if pada_idx < len(pada_labels) else "" label = TypstText( - set_font(f"#text(fill: white, size: 0.85em)[{label_text}]", INTRO_FONT), + set_font(f"#text(fill: white, size: 0.85em)[{label_text}]", SANSKRIT_FONT), scale=SCALE, ) label.points.next_to(texttttt.get_label(c_label), LEFT) @@ -169,7 +201,7 @@ def sloka_group_english(sloka: Sloka) -> Group[TypstText]: def sloka_thumbnail(sloka: Sloka) -> Group: - sloka_text = sloka_group(sloka) + sloka_text = sloka_group_reformed(sloka) if sloka.number is not None: number_label = Group( Rect(0.4, 0.4, fill_alpha=0.3), diff --git a/nirukta/timelines/explain_sloka.py b/nirukta/timelines/explain_sloka.py index 8409b77..9373b35 100644 --- a/nirukta/timelines/explain_sloka.py +++ b/nirukta/timelines/explain_sloka.py @@ -6,7 +6,7 @@ from janim.logger import log from nirukta.models import Line, Sloka from nirukta.render import Awaken, Sleep -from nirukta.sloka import sloka_group, sloka_thumbnail +from nirukta.sloka import sloka_thumbnail from nirukta.timelines import ( LenientTransformMatchingDiff, UtteranceTimeline, diff --git a/nirukta/timelines/introduce_sloka.py b/nirukta/timelines/introduce_sloka.py index 47af9c3..295c814 100644 --- a/nirukta/timelines/introduce_sloka.py +++ b/nirukta/timelines/introduce_sloka.py @@ -16,7 +16,7 @@ LEFT, ) -from nirukta.constants import INTRO_FONT, SCALE +from nirukta.constants import SANSKRIT_FONT, SCALE from nirukta.models import Language, Sloka from nirukta.render import ( FlatAligned, @@ -24,7 +24,11 @@ transform_text, typst_code, ) -from nirukta.sloka import sloka_group, sloka_group_chandas, title_and_pada_labels +from nirukta.sloka import ( + sloka_group_chandas, + sloka_group_reformed, + title_and_pada_labels, +) from nirukta.timelines.transform import LenientTransformMatchingDiff @@ -42,7 +46,7 @@ def gui_color(self) -> str: return YELLOW def construct(self): - sloka_g = sloka_group(self.sloka) + sloka_g = sloka_group_reformed(self.sloka) for line in sloka_g: self.play(Write(line, duration=4.0)) @@ -79,7 +83,7 @@ def construct(self): if self.citation is not None and self.citation != "sloka": citation_text = TypstText( - set_font(typst_code(self.citation, Language.SANSKRIT), INTRO_FONT), + set_font(typst_code(self.citation, Language.SANSKRIT), SANSKRIT_FONT), scale=SCALE, ) print(citation_text.text) diff --git a/nirukta/timelines/recitation.py b/nirukta/timelines/recitation.py new file mode 100644 index 0000000..88df2fa --- /dev/null +++ b/nirukta/timelines/recitation.py @@ -0,0 +1,79 @@ +from dataclasses import dataclass + +from janim.imports import ( + ORIGIN, + FadeIn, + FadeOut, + Group, + RectClip, + Succession, + Timeline, + Transform, + Vect, + Wait, +) +from nirukta.models import Sloka +from nirukta.sloka import sloka_group_chandas, sloka_group_reformed +from nirukta.timelines import LenientTransformMatchingDiff + + +scaledown = 0.5 + + +def place_in_corner(clip: RectClip, corner: Vect): + # TODO: fiddle w this + clip.transform.set(scale=scaledown * 1.3) + clip.points.scale(scaledown) + clip.points.to_border(corner, buff=0) + + +@dataclass +class RecitationTimeline(Timeline): + sloka: Sloka + devanagari: bool + chandas: bool + corner: Vect + + def construct(self): + group = sloka_group_reformed(self.sloka, devanagari=True) + group_clip = RectClip(group, anchor=ORIGIN, border=True) + + self.play( + Succession( + FadeIn(group), + Wait(1.0), + # Aligned( + # left.anim.points.shift(LEFT * scaledown * 6), + # right.anim.points.shift(RIGHT * scaledown * 6), + # right.anim.points.scale(0.2) + # ), + Wait(1.0), + # FadeOut(Group(lt, left, rt, right)) + ) + ) + + if self.chandas: + blank = sloka_group_chandas(self.sloka, blank=True, matras=False) + chandas = sloka_group_chandas(self.sloka, blank=False, matras=False) + group_clip.apply(blank.text, blank.keys, chandas.text, chandas.keys) + + self.play( + Succession( + LenientTransformMatchingDiff(group, blank.text, duration=0.5), + Transform(blank.text, chandas.text, duration=0.5), + Wait(1.0), + FadeIn(chandas.keys, duration=0.5), + FadeOut( + Group( + group, + chandas.keys, + chandas.text, + ), + duration=0.5, + ), + ) + ) + else: + self.play( + Succession(Wait(2.5), FadeOut(Group(group, group_clip), duration=0.5)) + ) diff --git a/nirukta/timelines/sutra_file.py b/nirukta/timelines/sutra_file.py index 97a9ef8..c4d7bb1 100644 --- a/nirukta/timelines/sutra_file.py +++ b/nirukta/timelines/sutra_file.py @@ -8,6 +8,7 @@ ORIGIN, RIGHT, UL, + DL, UP, UR, WHITE, @@ -26,7 +27,7 @@ Wait, Write, ) -from nirukta.constants import INACTIVE, INTRO_FONT, SCALE +from nirukta.constants import INACTIVE, SANSKRIT_FONT, SCALE from nirukta.models import Language, Sloka, SutraFile from nirukta.timelines import ( IntroduceSloka, @@ -43,8 +44,8 @@ from nirukta.sloka import ( sloka_group_chandas, sloka_group_english, + sloka_group_reformed, sloka_thumbnail, - sloka_group, ) from nirukta.timelines.explain_sloka import ExplainSloka, build_explain_sloka_cached @@ -69,7 +70,7 @@ def gui_color(self) -> str: def construct(self): citation = TypstText( - set_font(typst_code(self.citation, Language.SANSKRIT), INTRO_FONT), + set_font(typst_code(self.citation, Language.SANSKRIT), SANSKRIT_FONT), scale=SCALE, ) citation.points.move_to(ORIGIN) @@ -83,17 +84,16 @@ def construct(self): self.play(animation) # Listening side by side with pronunciation guide - for sloka in self.slokas: - sloka.meter() - # slp1 = sloka.slp1() - - # sloka. - # verse = MI.identify_meter(slp1) # from_scheme auto-detected; output IAST - # print(verse.meter_label) - # print(verse.summarize()) - # verse = MI.identify_meter(slp1, resplit_option='none') - # verse = MI.identify_meter(slp1, from_scheme='SLP', resplit_option='resplit_lite') + # for sloka in self.slokas: + # sloka.meter() + # slp1 = sloka.slp1() + # sloka. + # verse = MI.identify_meter(slp1) # from_scheme auto-detected; output IAST + # print(verse.meter_label) + # print(verse.summarize()) + # verse = MI.identify_meter(slp1, resplit_option='none') + # verse = MI.identify_meter(slp1, from_scheme='SLP', resplit_option='resplit_lite') for sloka in self.slokas: # introduction = IntroduceSloka(sloka).build().to_item() @@ -102,33 +102,51 @@ def construct(self): # right = sloka_group(sloka) # left.points.scale(0.5) # right.points.scale(0.5) - # left.points.to_border(UL, buff=MED_SMALL_BUFF) # right.points.to_border(UR, buff=MED_SMALL_BUFF) scaledown = 0.5 - lt = sloka_group(sloka) - rt = sloka_group(sloka) - left = RectClip(lt, anchor=ORIGIN, border=False) - left.points.scale(scaledown) - left.transform.set(scale=scaledown) - right = RectClip(rt, anchor=ORIGIN, border=False) - right.transform.set(scale=scaledown) - right.points.scale(scaledown) + def place_in_corner(clip, corner): + # TODO: fiddle w this + clip.transform.set(scale=scaledown * 1.3) + clip.points.scale(scaledown) + clip.points.to_border(corner, buff=0) + listen_deva = sloka_group_reformed(sloka, devanagari=True) + speak_deva = sloka_group_reformed(sloka, devanagari=True) - self.play( + listen_iast = sloka_group_reformed(sloka, devanagari=False) + # speak_iast = sloka_group_reformed(sloka, devanagari=False) + + listen_deva_clip = RectClip(listen_deva, anchor=ORIGIN, border=True) + speak_deva_clip = RectClip(speak_deva, anchor=ORIGIN, border=True) + listen_iast_clip = RectClip(listen_iast, anchor=ORIGIN, border=True) + # speak_iast_clip = RectClip(speak_iast, anchor=ORIGIN, border=True) + + place_in_corner(listen_deva_clip, UL) + place_in_corner(listen_iast_clip, DL) + place_in_corner(speak_deva_clip, UR) + self.play( Succession( - FadeIn(Group(lt, left, rt, right)), - Wait(1.0), - Aligned( - left.anim.points.shift(LEFT * scaledown * 6), - right.anim.points.shift(RIGHT * scaledown * 6), + FadeIn( + Group( + listen_deva, + listen_deva_clip, + listen_iast, + listen_iast_clip, + speak_deva, + speak_deva_clip, + ) + ), + Wait(1.0), + # Aligned( + # left.anim.points.shift(LEFT * scaledown * 6), + # right.anim.points.shift(RIGHT * scaledown * 6), # right.anim.points.scale(0.2) - ), - Wait(1.0), - # FadeOut(Group(lt, left, rt, right)) + # ), + Wait(1.0), + # FadeOut(Group(lt, left, rt, right)) ) ) @@ -137,44 +155,56 @@ def construct(self): g = Group(blank.text, blank.keys, chandas.text, chandas.keys) # g.points.shift(RIGHT * 3) - right.apply(*g) + speak_deva_clip.apply(*g) # blank.text.points.scale(0.5) # blank.text.points.to_border(UR, buff=MED_SMALL_BUFF) - self.play(LenientTransformMatchingDiff(rt, blank.text)) + self.play(LenientTransformMatchingDiff(speak_deva, blank.text)) self.play(Transform(blank.text, chandas.text)) self.play(Wait(1.0)) self.play(FadeIn(chandas.keys)) - self.play(FadeOut(Group(lt, left, chandas.keys, chandas.text, right))) + self.play( + FadeOut( + Group( + listen_deva, + listen_deva_clip, + listen_iast, + listen_iast_clip, + chandas.keys, + chandas.text, + speak_deva_clip, + ) + ) + ) # group.points.scale(factor) - # scale_with_stroke(left, 0.5) - # scale_with_stroke(right, 0.5) - # introduction.show() - # self.forward_to(introduction.end) - - # for sloka in self.slokas: - # # explain = build_explain_sloka_cached(sloka).to_item().show() - # explain = ExplainSloka(sloka).build().to_item().show() - # self.forward_to(explain.end) - - # thumbnail = sloka_thumbnail(sloka) - # initial = sloka_group(sloka) - # self.play(Write(initial), duration=0.33) - # self.play(LenientTransformMatchingDiff(initial, thumbnail[0]), duration=0.33) - # self.play(Aligned(FadeIn(thumbnail[1:]), Sleep(thumbnail[0]))) - # - # for li, line in enumerate(sloka.lines): - # for vi, vAkya in enumerate(line.vAkyAni): - # if li != 0 or vi != 0: - # self.play(Sleep(thumbnail[0])) - # - # selection = thumbnail[0][li].get_label( - # f"line_{li}_utterance_{vi}" - # ) - # self.play(Awaken(selection)) - # - # vt = UtteranceTimeline(vAkya).build().to_item().show() - # self.forward_to(vt.end) - # - # self.play(FadeOut(thumbnail)) + # scale_with_stroke(left, 0.5) + # scale_with_stroke(right, 0.5) + # introduction.show() + # self.forward_to(introduction.end) + + # for sloka in self.slokas: + # # explain = build_explain_sloka_cached(sloka).to_item().show() + # explain = ExplainSloka(sloka).build().to_item().show() + # self.forward_to(explain.end) + + # thumbnail = sloka_thumbnail(sloka) + # initial = sloka_group(sloka) + # self.play(Write(initial), duration=0.33) + # self.play(LenientTransformMatchingDiff(initial, thumbnail[0]), duration=0.33) + # self.play(Aligned(FadeIn(thumbnail[1:]), Sleep(thumbnail[0]))) + # + # for li, line in enumerate(sloka.lines): + # for vi, vAkya in enumerate(line.vAkyAni): + # if li != 0 or vi != 0: + # self.play(Sleep(thumbnail[0])) + # + # selection = thumbnail[0][li].get_label( + # f"line_{li}_utterance_{vi}" + # ) + # self.play(Awaken(selection)) + # + # vt = UtteranceTimeline(vAkya).build().to_item().show() + # self.forward_to(vt.end) + # + # self.play(FadeOut(thumbnail)) diff --git a/nirukta/typst.py b/nirukta/typst.py new file mode 100644 index 0000000..cf4b749 --- /dev/null +++ b/nirukta/typst.py @@ -0,0 +1,40 @@ +from typing import List, Optional + + +cell_width = 1.6 +gutter = 0.2 + + +def box_cell( + content: str, + width: Optional[float] = cell_width, + idx: Optional[int] = None, + fill: Optional[str] = None, +) -> str: + # Fallback to blank + if fill is None: + fill = "rgb(0, 0, 0, 0)" + + return ( + f"[#box(fill: {fill}, width: {width}em, height: {cell_width}em, radius: 0.4em)" + f"[#align(center + horizon)[#text(fill: white)[{content}]]]" + f"{'' if idx is None else f''}]" + ) + + +def arrange_horizontal( + cells: List[str], + # columns: int, + idx: Optional[int] = None, +) -> str: + return ( + f"[#box" + f"[#grid(columns: (auto,) * {len(cells)}, gutter: {gutter}em, {', '.join(cells)})]" + f"{'' if idx is None else f''}]" + ) + + +def arrange_vertical(cells: List[str], gutter: float = gutter) -> str: + return ( + f"#grid(rows: (auto,) * {len(cells)}, gutter: {gutter}em, {', '.join(cells)})" + ) From 0400c1640f09621f9ce2ba1f5e491caf2641b323 Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Mon, 25 May 2026 12:47:34 -0400 Subject: [PATCH 18/50] experimenting --- nirukta/timelines/recitation.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/nirukta/timelines/recitation.py b/nirukta/timelines/recitation.py index 88df2fa..c333c76 100644 --- a/nirukta/timelines/recitation.py +++ b/nirukta/timelines/recitation.py @@ -1,6 +1,7 @@ from dataclasses import dataclass from janim.imports import ( + ORANGE, ORIGIN, FadeIn, FadeOut, @@ -34,9 +35,17 @@ class RecitationTimeline(Timeline): chandas: bool corner: Vect + def __init__(self, sloka: Sloka, devanagari: bool, chandas: bool, corner: Vect): + super().__init__() + self.sloka = sloka + self.devanagari = devanagari + self.chandas = chandas + self.corner = corner + def construct(self): - group = sloka_group_reformed(self.sloka, devanagari=True) + group = sloka_group_reformed(self.sloka, devanagari=self.devanagari) group_clip = RectClip(group, anchor=ORIGIN, border=True) + place_in_corner(group_clip, self.corner) self.play( Succession( @@ -55,7 +64,8 @@ def construct(self): if self.chandas: blank = sloka_group_chandas(self.sloka, blank=True, matras=False) chandas = sloka_group_chandas(self.sloka, blank=False, matras=False) - group_clip.apply(blank.text, blank.keys, chandas.text, chandas.keys) + g = Group(blank.text, blank.keys, chandas.text, chandas.keys) + group_clip.apply(*g) self.play( Succession( @@ -66,6 +76,7 @@ def construct(self): FadeOut( Group( group, + group_clip, chandas.keys, chandas.text, ), From 46cd6e8f7b653b35a90813442ceb7df1140421f2 Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Mon, 25 May 2026 13:39:45 -0400 Subject: [PATCH 19/50] virtual insanity! --- .../sUtrARi/SrIsarasvatIstotraM_middle.sutra | 178 ++++++++++++++++++ nirukta/models/presentation/sloka.py | 3 + nirukta/sloka.py | 14 +- nirukta/timelines/recitation.py | 2 +- nirukta/timelines/sutra_file.py | 129 +++++++++---- 5 files changed, 290 insertions(+), 36 deletions(-) create mode 100644 library/sUtrARi/SrIsarasvatIstotraM_middle.sutra diff --git a/library/sUtrARi/SrIsarasvatIstotraM_middle.sutra b/library/sUtrARi/SrIsarasvatIstotraM_middle.sutra new file mode 100644 index 0000000..948d1b6 --- /dev/null +++ b/library/sUtrARi/SrIsarasvatIstotraM_middle.sutra @@ -0,0 +1,178 @@ +=== SrIsarasvatIstotraM agastyamuniproktam === + +=== sloka === + +--- line --- +sura[gods]+asurA[demons]+sevita[served]+pAda[feet]+paNkajA[lotus]=surAsurAsevitapAdapaNkajA +kare[in her hand] +virAjat[resplendently]+kamanIya[beautiful]+pustakA[book]=virAjatkamanIyapustakA . +"She whose lotus feet are served by both gods and demons, she who holds in her hand a resplendently beautiful book," + +--- line --- +viriYci[Brahma]+patnI[wife]=viriYcipatnI +kamala[lotus]+Asana[seat]+sTitA[situated]=kamalAsanasTitA +sarasvatI[Sarasvatī] +nftyatu[May][dance] +vAci[in][speech] +me[for me][my] +sadA[always] .. 3 .. +"the wife of Brahma, situated in a lotus seat. May Sarasvatī always dance for me in my speech." + + +--- line --- +sarasvatI[Sarasvatī] +sarasija[lotus]+kesara[filaments]+praBA[radiance]=sarasijakesarapraBA +tapasvinI[ascetic] +sita[white]+kamala[lotus]+Asana[seat]+priyA[fond of]=sitakamalAsanapriyA . +"Sarasvatī, whose radiance is like lotus filaments, the ascetic one, fond of a white lotus seat," + +--- line --- +Gana[full]+stanI[breasted]=GanastanI +kamala[lotus]+vilola[darting]+locanA[eyes]=kamalavilolalocanA manasvinI[high-minded] +Bavatu[may][be] +vara[boons]+prasAdinI[gracious]=varaprasAdinI .. 4 .. +"full breasted, with darting lotus eyes - may she be high-minded and gracious with her boons." + +=== sloka === + +--- line --- +sarasvati[O Sarasvatī] +(namas[reverence]+tuByam[to you]=namastuByam)=namastuByaM +varade[boon-giver] +kAmarUpiRi[form of longing] . +"O Sarasvatī, boon-giver, the very form of longing; reverence to you." + +--- line --- +(vidyA[learning]+AramBam[beginning]=vidyAramBam)=vidyAramBaM +karizyAmi[I will undertake] +"I will undertake the beginning of learning;" + +sidDiH[accomplishment]+Bavatu[may][arise]=sidDirBavatu +me[for me] +sadA[always] .. 5 .. +"may accomplishment arise for me, always." + +=== sloka === + +--- line --- +sarasvati[O Sarasvatī] +(namas[reverence]+tuByam[to you]=namastuByam)=namastuByaM +"O Sarasvatī, reverence to you." + +sarva[all]+devi[O Goddess]=sarvadevi +namaH[reverence]=namo namaH[reverence] . +"O Goddess of all, reverence, reverence." + +--- line --- +SAnta[tranquil]+rupa[form]=SAntarUpe +SaSi[moon]+Dare[bearer]=SaSiDare +sarva[all]+yoga[disciplines]=sarvayoge +namaH[reverence]=namo namaH[reverence] .. 6 .. +"O you of tranquil form, O bearer of the moon, O you in whom all disciplines reside, reverence, reverence." + +=== sloka === + +--- line --- +nitya[constant]+Ananda[in][bliss]=nityAnande +nir[un]+ADara[supported]=nirADAre +nizkala[undivided]=nizkalAyE namaH[reverence]=namo namaH[reverence] . +"O you in constant bliss, O unsupported one - reverence, reverence to her who is undivided." + +--- line --- +vidyA[knowledge]+Dara[bearer]=vidyADare +viSA[large]+lAkzi[eyed]=viSAlAkzi +SudDa[pure]+jYana[knowledge]=SudDajYAne +namaH[reverence]=namo namaH[reverence] .. 7 .. +"O bearer of knowledge, O large eyed one, O embodier of pure knowledge, reverence, reverence." + +=== sloka === + +--- line --- +(SudDa[pure]+sPawika[crystal]+rUpa[form]=SudDasPawikarUpa)=SudDasPawikarUpAyE +sUkzma[subtle]+rupa[form]=sUkzmarUpe +namaH[reverence]=namo namaH[reverence] . +"O you of subtle form - reverence, reverence to her of pure crystal form." + +--- line --- +Sabda[Sabda]+brahma[Brahman]=Sabdabrahmi +catur[four]+hasta[handed]=caturhaste sarva[all]+sidDa[accomplishment]=sarvasidDyE +namaH[reverence]=namo namaH[reverence] .. 8 .. +"O you who are Sabda Brahman, O four-handed one - reverence, reverence to her who is all accomplishment." + +=== sloka === + +--- line --- +mukta[pearls]+AlaNkfta[adorned]+sarva[every]+aNga[limb]=muktAlaNkftasarvANgyE +mUlADAra[root chakra]=mUlADAre +namaH[reverence]=namo namaH[reverence] . +"O you who are the root chakra - reverence, reverence to her whose every limb is adorned by pearls." + +--- line --- +mUla[basis]+mantra[mantra]+svarUpa[self-form]=mUlamantrasvarUpAyE +mUla[origin]+Sakta[ability]=mUlaSaktyE +namaH[Reverence]=namo namaH[reverence] .. 9 .. +"Reverence, reverence to her whose self-form is the basis of mantra itself, to her who is the origin of ability." + +=== sloka === + +--- line --- +manas[mind]+mayi[consist]=manomayi +mahA[great]+yoga[yoga]=mahAyoge +vAc[speech]+ISvarI[Goddess]=vAgISvari +namaH[reverence]=namo namaH[reverence] . +"O you who consist of the mind, O embodier of great yoga, O Goddess of speech, reverence, reverence." + +--- line --- +vARa[music]=vARyE +vara[boon]+da[giving]+hasta[hand]=varadahastAyE +vara[boon]+dA[giver]=varadAyE +namaH[Reverence]=namo namaH[reverence] .. 10 .. +"Reverence, reverence to the embodiment of music, to her who extends a boon-giving hand, to the giver of boons." + +=== sloka === + +--- line --- +veda[knowledge]=vedyAyE +veda[knowledge]+rUpa[form]=vedarUpAyE +veda[Vedas]+antara[culmination]=vedAntAyE +namaH[Reverence]=namo namaH[reverence] . +"Reverence, reverence to her who is knowledge itself, to her in the form of knowledge, to her who is the culmination of all the Vedas." + +--- line --- +guRa[virtues]+doza[faults]+vivarjita[free from]=guRadozavivarjinyE +guRa[qualities]+dIpta[shining]=guRadIptyE +namaH[Reverence]=namo namaH[reverence] .. 11 .. +"Reverence, reverence to her who is free from virtues and faults, to her of shining qualities." + +=== sloka === + +--- line --- +sarva[all]+jYAna[knowledge]=sarvajYAne +sadA[always]+Ananda[bliss]=sadAnande +sarva[all]+rUpa[form]=sarvarUpe +namaH[reverence]=namo namaH[reverence] . +"O you who are all knowledge, O you who are always in bliss, O you who are all form - reverence, reverence." + +--- line --- +sampanna[perfect]=sampannAyE +kumAra[youth]=kumAryE ca[and] +sarvajYa[omniscient]=sarvajYAyE +namaH[reverence]=namo namaH[reverence] .. 12 .. +"O omniscient one - reverence, reverence to her who is perfect and to her who is youth." + +=== sloka === + +--- line --- +yoga[yoga]+rUpa[form]=yogarUpe +ramA[Lakṣmī]+devI[Goddess]=ramAdevyE +yoga[yoga]+Ananda[bliss]=yogAnande +namaH[reverence]=namo namaH[reverence] . +"O you who are the form of yoga, O embodiment of the bliss of yoga - reverence, reverence to that Goddess Lakṣmī." + +--- line --- +divya[divine]+jYA[knower]=divyajYAyE +tri[three]+netra[eyed]=trinetrAyE +divya[divine]+mUrtI[embodiment]=divyamUrtyE +namaH[Reverence]=namo namaH[reverence] .. 13 .. +"Reverence, reverence to the knower of the divine, to the three-eyed one, to she who has divine embodiment." + diff --git a/nirukta/models/presentation/sloka.py b/nirukta/models/presentation/sloka.py index 036ba30..4e80f7b 100644 --- a/nirukta/models/presentation/sloka.py +++ b/nirukta/models/presentation/sloka.py @@ -67,4 +67,7 @@ def meter(self) -> tuple[str, List[List[Akshara]]]: label = transliterate.process("IAST", "SLP1", verse.meter_label) assert label is not None + if "(" in label: + label = label.split("(")[0].strip() + return (label, padas) diff --git a/nirukta/sloka.py b/nirukta/sloka.py index 09c9001..3ea4dfe 100644 --- a/nirukta/sloka.py +++ b/nirukta/sloka.py @@ -107,8 +107,16 @@ def sloka_group_chandas( sloka: Sloka, blank: bool = False, matras: bool = False, + devanagari: bool = True, ) -> Keyed: - base_width = 1.6 + if devanagari: + lang = Language.SANSKRIT + font = SANSKRIT_FONT + else: + lang = Language.TRANSLIT + font = LATIN_FONT + + base_width = 1.8 gutter = 0.2 all_cells = [] @@ -118,7 +126,7 @@ def sloka_group_chandas( for pada in padas: for akshara in pada: - deva = transform_text(akshara.text, Language.SANSKRIT) + deva = transform_text(akshara.text, lang) fill = None if blank else akshara.rgb_color() all_cells.append( box_cell( @@ -147,7 +155,7 @@ def sloka_group_chandas( row_labels.append(row_label) grid_code = f"#grid(rows: (auto,) * {n}, gutter: {gutter}em, {', '.join(rows)})" - grid = TypstText(set_font(grid_code, SANSKRIT_FONT), scale=SCALE) + grid = TypstText(set_font(grid_code, font), scale=SCALE) if blank: return Keyed(text=grid, keys=Group()) diff --git a/nirukta/timelines/recitation.py b/nirukta/timelines/recitation.py index c333c76..a735076 100644 --- a/nirukta/timelines/recitation.py +++ b/nirukta/timelines/recitation.py @@ -23,7 +23,7 @@ def place_in_corner(clip: RectClip, corner: Vect): # TODO: fiddle w this - clip.transform.set(scale=scaledown * 1.3) + clip.transform.set(scale=scaledown * 1.2) clip.points.scale(scaledown) clip.points.to_border(corner, buff=0) diff --git a/nirukta/timelines/sutra_file.py b/nirukta/timelines/sutra_file.py index c4d7bb1..b3e6510 100644 --- a/nirukta/timelines/sutra_file.py +++ b/nirukta/timelines/sutra_file.py @@ -10,7 +10,9 @@ UL, DL, UP, + DOWN, UR, + DR, WHITE, Aligned, FadeIn, @@ -18,6 +20,7 @@ Group, Rect, RectClip, + ShrinkToEdge, Succession, SurroundingRect, Text, @@ -38,6 +41,7 @@ Awaken, Sleep, scale_with_stroke, + transform_text, set_font, typst_code, ) @@ -95,6 +99,18 @@ def construct(self): # verse = MI.identify_meter(slp1, resplit_option='none') # verse = MI.identify_meter(slp1, from_scheme='SLP', resplit_option='resplit_lite') + listen = "SravaRa" + # recite = "pAWa" + # title = TypstText( + # set_font( + # f"#text(fill: white, size: 1.2em)[{transform_text(listen, Language.SANSKRIT)}]", + # SANSKRIT_FONT, + # ), + # scale=SCALE, + # ) + # title.points.move_to(UL) + # self.play(FadeIn(title)) + for sloka in self.slokas: # introduction = IntroduceSloka(sloka).build().to_item() @@ -116,28 +132,44 @@ def place_in_corner(clip, corner): speak_deva = sloka_group_reformed(sloka, devanagari=True) listen_iast = sloka_group_reformed(sloka, devanagari=False) - # speak_iast = sloka_group_reformed(sloka, devanagari=False) + speak_iast = sloka_group_reformed(sloka, devanagari=False) listen_deva_clip = RectClip(listen_deva, anchor=ORIGIN, border=True) speak_deva_clip = RectClip(speak_deva, anchor=ORIGIN, border=True) listen_iast_clip = RectClip(listen_iast, anchor=ORIGIN, border=True) - # speak_iast_clip = RectClip(speak_iast, anchor=ORIGIN, border=True) + speak_iast_clip = RectClip(speak_iast, anchor=ORIGIN, border=True) place_in_corner(listen_deva_clip, UL) place_in_corner(listen_iast_clip, DL) place_in_corner(speak_deva_clip, UR) + place_in_corner(speak_iast_clip, DR) + + listen_deva.points.shift(DOWN * 6) + listen_deva.points.scale(0.5) + listen_iast.points.shift(DOWN * 6) + listen_iast.points.scale(0.5) self.play( Succession( - FadeIn( - Group( - listen_deva, - listen_deva_clip, - listen_iast, - listen_iast_clip, - speak_deva, - speak_deva_clip, - ) + Aligned( + Aligned( + listen_deva.anim.points.scale(2.0), + listen_deva.anim.points.shift(UP * 6), + listen_iast.anim.points.scale(2.0), + listen_iast.anim.points.shift(UP * 6), + ), + FadeIn( + Group( + listen_deva, + listen_deva_clip, + listen_iast, + listen_iast_clip, + speak_deva, + speak_deva_clip, + speak_iast, + speak_iast_clip, + ) + ), ), Wait(1.0), # Aligned( @@ -150,32 +182,65 @@ def place_in_corner(clip, corner): ) ) - blank = sloka_group_chandas(sloka, blank=True, matras=False) - chandas = sloka_group_chandas(sloka, blank=False, matras=False) - g = Group(blank.text, blank.keys, chandas.text, chandas.keys) - # g.points.shift(RIGHT * 3) + blank_deva = sloka_group_chandas( + sloka, blank=True, matras=False, devanagari=True + ) + chandas_deva = sloka_group_chandas( + sloka, blank=False, matras=False, devanagari=True + ) + blank_iast = sloka_group_chandas( + sloka, blank=True, matras=False, devanagari=False + ) + chandas_iast = sloka_group_chandas( + sloka, blank=False, matras=False, devanagari=False + ) - speak_deva_clip.apply(*g) - # blank.text.points.scale(0.5) - # blank.text.points.to_border(UR, buff=MED_SMALL_BUFF) + speak_deva_clip.apply( + blank_deva.text, blank_deva.keys, chandas_deva.text, chandas_deva.keys + ) + speak_iast_clip.apply( + blank_iast.text, blank_iast.keys, chandas_iast.text, chandas_iast.keys + ) - self.play(LenientTransformMatchingDiff(speak_deva, blank.text)) - self.play(Transform(blank.text, chandas.text)) - self.play(Wait(1.0)) - self.play(FadeIn(chandas.keys)) self.play( - FadeOut( - Group( - listen_deva, - listen_deva_clip, - listen_iast, - listen_iast_clip, - chandas.keys, - chandas.text, - speak_deva_clip, - ) + Aligned( + LenientTransformMatchingDiff(speak_deva, blank_deva.text), + LenientTransformMatchingDiff(speak_iast, blank_iast.text), ) ) + self.play( + Aligned( + Transform(blank_deva.text, chandas_deva.text), + Transform(blank_iast.text, chandas_iast.text), + ) + ) + self.play(Wait(1.0)) + self.play(FadeIn(Group(chandas_deva.keys, chandas_iast.keys))) + self.prepare( + Aligned( + Aligned( + listen_deva.anim.points.scale(0.5), + listen_deva.anim.points.shift(UP * 6), + listen_iast.anim.points.scale(0.5), + listen_iast.anim.points.shift(UP * 6), + ), + FadeOut( + Group( + listen_deva_clip, + listen_iast_clip, + chandas_deva.keys, + chandas_deva.text, + chandas_iast.keys, + chandas_iast.text, + speak_deva_clip, + speak_iast_clip, + ) + ), + ), + duration=1.0, + ) + + # self.play(FadeOut(title)) # group.points.scale(factor) # scale_with_stroke(left, 0.5) From c104cca381276ca612cb348ad056f598fc2719a4 Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Mon, 25 May 2026 14:27:46 -0400 Subject: [PATCH 20/50] experimenting --- nirukta/models/presentation/sloka.py | 4 +- nirukta/render.py | 6 ++- nirukta/timelines/sutra_file.py | 65 ++++++++++++++++++---------- 3 files changed, 48 insertions(+), 27 deletions(-) diff --git a/nirukta/models/presentation/sloka.py b/nirukta/models/presentation/sloka.py index 4e80f7b..ba0d5d6 100644 --- a/nirukta/models/presentation/sloka.py +++ b/nirukta/models/presentation/sloka.py @@ -67,7 +67,7 @@ def meter(self) -> tuple[str, List[List[Akshara]]]: label = transliterate.process("IAST", "SLP1", verse.meter_label) assert label is not None - if "(" in label: - label = label.split("(")[0].strip() + # if "(" in label: + label = label.split(" ")[0].strip() return (label, padas) diff --git a/nirukta/render.py b/nirukta/render.py index 806fa7a..c738cda 100644 --- a/nirukta/render.py +++ b/nirukta/render.py @@ -196,7 +196,11 @@ def T(s): def set_font(text: str, font: str): - return f'#set text(font: "{font}", stroke: none)\n#set page(width: {266 * SCALE}pt)\n{text}' + return ( + f'#set text(font: "{font}", stroke: none)\n' + # f"#set page(width: {266 * SCALE}pt)\n" + f"{text}" + ) def text_box(text: str, color: str, stroke_mode: bool = False): diff --git a/nirukta/timelines/sutra_file.py b/nirukta/timelines/sutra_file.py index b3e6510..cf60614 100644 --- a/nirukta/timelines/sutra_file.py +++ b/nirukta/timelines/sutra_file.py @@ -124,7 +124,7 @@ def construct(self): def place_in_corner(clip, corner): # TODO: fiddle w this - clip.transform.set(scale=scaledown * 1.3) + clip.transform.set(scale=scaledown) clip.points.scale(scaledown) clip.points.to_border(corner, buff=0) @@ -132,17 +132,21 @@ def place_in_corner(clip, corner): speak_deva = sloka_group_reformed(sloka, devanagari=True) listen_iast = sloka_group_reformed(sloka, devanagari=False) - speak_iast = sloka_group_reformed(sloka, devanagari=False) + # speak_iast = sloka_group_reformed(sloka, devanagari=False) listen_deva_clip = RectClip(listen_deva, anchor=ORIGIN, border=True) speak_deva_clip = RectClip(speak_deva, anchor=ORIGIN, border=True) listen_iast_clip = RectClip(listen_iast, anchor=ORIGIN, border=True) - speak_iast_clip = RectClip(speak_iast, anchor=ORIGIN, border=True) + # speak_iast_clip = RectClip(speak_iast, anchor=ORIGIN, border=True) place_in_corner(listen_deva_clip, UL) - place_in_corner(listen_iast_clip, DL) - place_in_corner(speak_deva_clip, UR) - place_in_corner(speak_iast_clip, DR) + place_in_corner(listen_iast_clip, UR) + place_in_corner(speak_deva_clip, DOWN) + # speak_deva.points.scale((2.0, 1.0, 1.0)) + speak_deva_clip.points.scale((2.0, 1.0, 1.0)) + + iiiii = SurroundingRect(speak_deva, buff=0) + # place_in_corner(speak_iast_clip, DR) listen_deva.points.shift(DOWN * 6) listen_deva.points.scale(0.5) @@ -160,14 +164,15 @@ def place_in_corner(clip, corner): ), FadeIn( Group( + iiiii, listen_deva, listen_deva_clip, listen_iast, listen_iast_clip, speak_deva, speak_deva_clip, - speak_iast, - speak_iast_clip, + # speak_iast, + # speak_iast_clip, ) ), ), @@ -188,34 +193,45 @@ def place_in_corner(clip, corner): chandas_deva = sloka_group_chandas( sloka, blank=False, matras=False, devanagari=True ) - blank_iast = sloka_group_chandas( - sloka, blank=True, matras=False, devanagari=False - ) - chandas_iast = sloka_group_chandas( - sloka, blank=False, matras=False, devanagari=False - ) + # blank_iast = sloka_group_chandas( + # sloka, blank=True, matras=False, devanagari=False + # ) + # chandas_iast = sloka_group_chandas( + # sloka, blank=False, matras=False, devanagari=False + # ) speak_deva_clip.apply( - blank_deva.text, blank_deva.keys, chandas_deva.text, chandas_deva.keys - ) - speak_iast_clip.apply( - blank_iast.text, blank_iast.keys, chandas_iast.text, chandas_iast.keys + iiiii, + blank_deva.text, + blank_deva.keys, + chandas_deva.text, + chandas_deva.keys, ) + # speak_iast_clip.apply( + # blank_iast.text, blank_iast.keys, chandas_iast.text, chandas_iast.keys + # ) self.play( Aligned( LenientTransformMatchingDiff(speak_deva, blank_deva.text), - LenientTransformMatchingDiff(speak_iast, blank_iast.text), + # LenientTransformMatchingDiff(speak_iast, blank_iast.text), ) ) self.play( Aligned( Transform(blank_deva.text, chandas_deva.text), - Transform(blank_iast.text, chandas_iast.text), + # Transform(blank_iast.text, chandas_iast.text), ) ) self.play(Wait(1.0)) - self.play(FadeIn(Group(chandas_deva.keys, chandas_iast.keys))) + self.play( + FadeIn( + Group( + chandas_deva.keys, + # chandas_iast.keys + ) + ) + ) self.prepare( Aligned( Aligned( @@ -230,10 +246,11 @@ def place_in_corner(clip, corner): listen_iast_clip, chandas_deva.keys, chandas_deva.text, - chandas_iast.keys, - chandas_iast.text, + # chandas_iast.keys, + # chandas_iast.text, speak_deva_clip, - speak_iast_clip, + iiiii, + # speak_iast_clip, ) ), ), From ef8d0558e2c962f1f19271a61ef176d14b219618 Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Mon, 25 May 2026 14:45:41 -0400 Subject: [PATCH 21/50] include embedded fonts --- nirukta/patches.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nirukta/patches.py b/nirukta/patches.py index efa3af8..42c6818 100644 --- a/nirukta/patches.py +++ b/nirukta/patches.py @@ -26,7 +26,7 @@ def _safe_mt_getattr(self, name): # Override fonts dir to include custom fonts font_dir = os.path.join(os.path.dirname(__file__), "..", "fonts") -tc._typst_fonts = typst.Fonts(False, False, [font_dir]) +tc._typst_fonts = typst.Fonts(False, True, [font_dir]) db = get_database() From cfb75c618cc65e9f183c1c67228331b2f5fa5885 Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Mon, 25 May 2026 14:45:57 -0400 Subject: [PATCH 22/50] transformableframeclip quadrant demo --- demo.py | 101 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 demo.py diff --git a/demo.py b/demo.py new file mode 100644 index 0000000..855d98b --- /dev/null +++ b/demo.py @@ -0,0 +1,101 @@ +# from janim.imports import * + +from janim.imports import ( + BLACK, + RED, + UP, + WHITE, + Axes, + Config, + Create, + Dot, + Group, + ItemUpdater, + Rect, + Timeline, + TransformableFrameClip, + TypstMath, +) +import nirukta.patches # pyright: ignore[reportUnusedImport] # noqa: F401 + +import math + + +class GraphDemonstration(Timeline): + def __init__(self, f, x_range, typ_code): + super().__init__() + self.f = f + self.x_range = x_range + self.typ_code = typ_code + + def construct(self): + axes = Axes(axis_config=dict(include_numbers=True)) + graph = axes.get_graph(self.f, self.x_range, color=RED, stroke_radius=0.05) + + typ = TypstMath( + self.typ_code, stroke_color=BLACK, stroke_alpha=1, stroke_background=True + ).show() + typ.points.scale(1.6).to_border(UP) + + def dots_updater(p): + points = graph.current().points + return Group( + Dot(points.get_start()), + Dot(points.get_end()), + fill_color=BLACK, + stroke_alpha=1, + ) + + self.forward() + self.play(Create(axes, lag_ratio=0.05)) + self.play( + Create(graph), + ItemUpdater(None, dots_updater), + ) + + +class MainTimeline(Timeline): + def construct(self): + params_list = [ + (lambda x: x**2, (-1, 1.5), "f(x) = x^2"), + (lambda x: x**3, (-1.5, 1.5), "f(x) = x^3"), + (lambda x: math.sin(x), (-3, 3), "f(x) = sin(x)"), + (lambda x: math.atan(x), (-2, 2), "f(x) = tan^(-1) x"), + ] + + cols, rows = 2, 2 + fw = Config.get.frame_width + fh = Config.get.frame_height + quad_w = fw / cols + quad_h = fh / rows + + # clip 1/4 from each side → each quadrant shows the central 1/2 × 1/2 + clip_h = 0.5 - 1 / (2 * cols) # 0.25 + clip_v = 0.5 - 1 / (2 * rows) # 0.25 + + for idx, params in enumerate(params_list): + col = idx % cols + row = idx // cols + + offset_x = -clip_h + col * (1 / cols) # -0.25 or +0.25 + offset_y = clip_v - row * (1 / rows) # 0.25 or -0.25 + + tl = ( + GraphDemonstration(*params).build().to_item(keep_last_frame=True).show() + ) + TransformableFrameClip( + tl, + clip=(clip_h, clip_v, clip_h, clip_v), + offset=(offset_x, offset_y), + ).show() + + center = ( + (col + 0.5 - cols / 2) * quad_w, + (rows / 2 - row - 0.5) * quad_h, + 0, + ) + border = Rect(quad_w, quad_h, color=WHITE, stroke_radius=0.03) + border.points.move_to(center) + border.show() + + self.forward(4) From 236617dcba3edbab158a113fc39e4270906ad602 Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Mon, 25 May 2026 14:53:27 -0400 Subject: [PATCH 23/50] fix demo --- demo.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/demo.py b/demo.py index 855d98b..ecfd630 100644 --- a/demo.py +++ b/demo.py @@ -85,8 +85,9 @@ def construct(self): ) TransformableFrameClip( tl, - clip=(clip_h, clip_v, clip_h, clip_v), + # clip=(clip_h, clip_v, clip_h, clip_v), offset=(offset_x, offset_y), + scale=0.5, ).show() center = ( From b392d9dc803dc8e06d8d0291d73eebac503c939f Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Mon, 25 May 2026 14:56:53 -0400 Subject: [PATCH 24/50] remove vidyut --- nirukta/chandas.py | 14 ------ nirukta/sloka.py | 3 +- pyproject.toml | 3 +- test.py | 114 --------------------------------------------- uv.lock | 44 ++--------------- 5 files changed, 6 insertions(+), 172 deletions(-) delete mode 100644 nirukta/chandas.py delete mode 100644 test.py diff --git a/nirukta/chandas.py b/nirukta/chandas.py deleted file mode 100644 index d820a76..0000000 --- a/nirukta/chandas.py +++ /dev/null @@ -1,14 +0,0 @@ -from pathlib import Path - -import vidyut -from vidyut.chandas import Chandas - -_DATA_DIR = Path(__file__).parents[2] / "vidyut_data" -_METERS_TSV = _DATA_DIR / "chandas" / "meters.tsv" - - -if not _METERS_TSV.exists(): - _DATA_DIR.mkdir(exist_ok=True) - vidyut.download_data(_DATA_DIR) - -chandas = Chandas(str(_METERS_TSV)) diff --git a/nirukta/sloka.py b/nirukta/sloka.py index 3ea4dfe..1aec021 100644 --- a/nirukta/sloka.py +++ b/nirukta/sloka.py @@ -19,7 +19,6 @@ from janim.imports import WHITE from nirukta.render import scale_with_stroke, set_font, transform_text, typst_code -from nirukta.chandas import chandas from typing import List from nirukta.typst import arrange_vertical, box_cell, arrange_horizontal @@ -170,7 +169,7 @@ def title_and_pada_labels( # Position title and labels relative to the centered grid meter_deva = transform_text(meter_label, Language.SANSKRIT) title = TypstText( - set_font(f"#text(fill: white, size: 1.4em)[{meter_deva}]", SANSKRIT_FONT), + set_font(f"#text(fill: white, size: 1.2em)[{meter_deva}]", SANSKRIT_FONT), scale=SCALE, ) title.points.next_to(texttttt, UP) diff --git a/pyproject.toml b/pyproject.toml index 6ac92ef..83efc3c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,10 +7,9 @@ requires-python = ">=3.13" dependencies = [ "aksharamukha>=2.3", "dill>=0.4.1", - "janim[gui]>=4.1.0", + "janim[gui]>=4.2.0", "parsimonious>=0.11.0", "skrutable>=2.6.3", - "vidyut>=0.4.0", ] [build-system] diff --git a/test.py b/test.py deleted file mode 100644 index b692191..0000000 --- a/test.py +++ /dev/null @@ -1,114 +0,0 @@ -import vidyut -from pathlib import Path -from vidyut.chandas import Chandas -from vidyut.cheda import Chedaka - -# from vidyut.kosha import Pada, Pratipadika -from vidyut.lipi import transliterate, Scheme -from vidyut.prakriya import ( - Dhatu, - # Gana, - Lakara, - Linga, - # Pada, - # Pratipadika, - Purusha, - Prayoga, - Vacana, - # Vibhakti, - Vyakarana, -) - -DATA_DIR = Path(__file__).parent / "vidyut_data" -METERS_TSV = DATA_DIR / "chandas" / "meters.tsv" - -if not METERS_TSV.exists(): - DATA_DIR.mkdir(exist_ok=True) - vidyut.download_data(DATA_DIR) - - -# ── 1. chandas ───────────────────────────────────────────────────────────────── -# Identify meter and classify syllable weights - -print("─" * 60) -print("1. CHANDAS — meter identification") -print("─" * 60) - -padas_slp1 = [ - "vakratuRqa mahAkAya", - "sUryakowi samapraBa", - "nirviGnaM kuru me deva", - "sarva kAryezu sarvadA", -] - -c = Chandas(str(METERS_TSV)) -chedaka = Chedaka(str(DATA_DIR)) - -_doc_match = c.classify("mAtaH samastajagatAM maDukEwaBAreH") -assert _doc_match.padya == "vasantatilakA", ( - f"Sanity check failed: got {_doc_match.padya}" -) -print("Sanity check passed: vasantatilakā identified correctly.\n") - -for i, slp1 in enumerate(padas_slp1): - iast = transliterate(slp1, Scheme.Slp1, Scheme.Iast) - match = c.classify(slp1) - weights = " ".join(akshara.weight for pada in match.aksharas for akshara in pada) - print(f"Pada {i + 1}: {slp1}") - print(f" Meter : {match.padya}") - print(f" G/L : {weights}\n") - tokens = chedaka.run(slp1) - for token in tokens: - print(transliterate(token.lemma, Scheme.Slp1, Scheme.Iast)) - print(token) - print() - - -# ── 2. prakriya ──────────────────────────────────────────────────────────────── -# Derive word forms by applying Paninian grammar rules step-by-step. -# Using words from the sloka itself: kuru (do!) and deva (god). - -# print("─" * 60) -# print("2. PRAKRIYA — Paninian derivation") -# print("─" * 60) -# -# v = Vyakarana() - -# # kuru — imperative 2sg of kṛ (to do), the verb in "kuru me deva" -# # aupadeshika (dhatupatha) form of kṛ is qukf\Y, tanadi (8th) gana -# dhatu_kr = Dhatu.mula(r"qukf\Y", Gana.Tanadi) -# pada_kuru = Pada.Tinanta( -# dhatu_kr, Prayoga.Kartari, Lakara.Lot, Purusha.Madhyama, Vacana.Eka -# ) -# results = v.derive(pada_kuru) -# print(f"kṛ (to do) → imperative 2sg: {[r.text for r in results]}") -# print("Derivation steps for 'kuru':") -# kuru_result = next(r for r in results if r.text == "kuru") -# for step in kuru_result.history: -# print(f" [{step.code}] {' + '.join(step.result)}") -# -# print() -# -# devaḥ — nominative singular masculine of deva (god) -# prati_deva = Pratipadika.basic("deva") -# pada_devah = Pada.Subanta(prati_deva, Linga.Pum, Vibhakti.Prathama, Vacana.Eka) -# results = v.derive(pada_devah) -# print(f"deva (god) → nominative sg masc: {[r.text for r in results]}") -# print("Derivation steps for 'devaḥ':") -# for step in results[0].history: -# print(f" [{step.code}] {' + '.join(step.result)}") -# -# print() - - -# ── 3. cheda ─────────────────────────────────────────────────────────────────── -# Segment and morphologically tag a Sanskrit sentence. - -# print("─" * 60) -# print("3. CHEDA — word segmentation + morphological tagging") -# print("─" * 60) - - -# Use the third pada in SLP1 -# slp1_pada = transliterate("nirvighnaṃ kuru me deva", Scheme.Iast, Scheme.Slp1) -# print(f"Input (SLP1): {slp1_pada}\n") diff --git a/uv.lock b/uv.lock index af86125..a65cd00 100644 --- a/uv.lock +++ b/uv.lock @@ -246,7 +246,7 @@ wheels = [ [[package]] name = "janim" -version = "4.1.0" +version = "4.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, @@ -265,9 +265,9 @@ dependencies = [ { name = "tqdm" }, { name = "typst" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bf/8e/04314dc7562e9e3e4005ef94f849b509fe06cb16912b46a4d2fb65bc30be/janim-4.1.0.tar.gz", hash = "sha256:d5024c19de5192cadfdd4d6032fecd15d74432448ffa28d434cd62a5e00780b5", size = 345586, upload-time = "2026-04-07T05:37:39.534Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7f/53/24a5fb0fe5c846b5b5076c45f4e1d4f5004744f360d65c477dc9122bd08e/janim-4.2.0.tar.gz", hash = "sha256:5e92cff148a92fb611ff829ab1d9916f95343233b1a86b4d1f8a5bb866f8c190", size = 377543, upload-time = "2026-05-23T06:17:00.852Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/08/6f/6efc65e58ecb81991ef32ea7a713cf7c8af5d56465cbf4f7a9b44a3678f8/janim-4.1.0-py3-none-any.whl", hash = "sha256:28c9650321707ae6ea6ca09bdafd145bfe298dd5766bd4e505534cfbbc076935", size = 462015, upload-time = "2026-04-07T05:37:37.922Z" }, + { url = "https://files.pythonhosted.org/packages/51/86/0d0a8719e7d137470a0f8c4143f30b07672a96a3b798f229671cc61a3d9c/janim-4.2.0-py3-none-any.whl", hash = "sha256:edef1c4df6854b443e13d91debbb3147a168ffed5020298ed48e820ff32a4797", size = 502856, upload-time = "2026-05-23T06:16:58.989Z" }, ] [package.optional-dependencies] @@ -451,17 +451,15 @@ dependencies = [ { name = "janim", extra = ["gui"] }, { name = "parsimonious" }, { name = "skrutable" }, - { name = "vidyut" }, ] [package.metadata] requires-dist = [ { name = "aksharamukha", specifier = ">=2.3" }, { name = "dill", specifier = ">=0.4.1" }, - { name = "janim", extras = ["gui"], specifier = ">=4.1.0" }, + { name = "janim", extras = ["gui"], specifier = ">=4.2.0" }, { name = "parsimonious", specifier = ">=0.11.0" }, { name = "skrutable", specifier = ">=2.6.3" }, - { name = "vidyut", specifier = ">=0.4.0" }, ] [[package]] @@ -1006,40 +1004,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, ] -[[package]] -name = "vidyut" -version = "0.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/69/b5/6876b3f807ac38812c6e29eb43738d28a6a21c2aed456486e654997efcdd/vidyut-0.4.0.tar.gz", hash = "sha256:fb776ec751e66c8d44bd94ef71008f9ea05284f51055aa009cfaef33ccf5f454", size = 943090, upload-time = "2025-01-22T07:28:01.479Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/27/92/dfd27d8d4fa19d22001533399396a2fe9ec57c97bbc50c5352150ca0a96d/vidyut-0.4.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:cae462b70772e30029c4e2f0c1ee6a39ae4bff43ac755998efb3ca532c97147f", size = 2512816, upload-time = "2025-01-22T07:27:26.362Z" }, - { url = "https://files.pythonhosted.org/packages/89/01/40f37a8a821b434dc021773fad9ef4191e3a5b579cbbbde1cf41e18073b0/vidyut-0.4.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b8db5d2bed6331b0af14b200876d36d5f3b9705c186204ebdb5c4d1c7e9da1df", size = 2439225, upload-time = "2025-01-22T07:27:22.248Z" }, - { url = "https://files.pythonhosted.org/packages/6c/2c/7d7d4cfa59e36927f603ef9844a664ef7e51ed5a00534e4882f3def31146/vidyut-0.4.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0115fdada49c40b6affe4622ff1d2f0ca8761d8235544e713a25b4f18a8a0366", size = 18587289, upload-time = "2025-01-22T07:26:35.987Z" }, - { url = "https://files.pythonhosted.org/packages/56/f3/5b9f5bef2218f5dc5d3078b3269be8616f28ac6923c81e1dbee6ac342cff/vidyut-0.4.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:981d7230696cb0ba071353aa12b113b3c23a800812c5afe5617e74aca5774561", size = 17850731, upload-time = "2025-01-22T07:26:43.513Z" }, - { url = "https://files.pythonhosted.org/packages/9a/f8/54a4768445bdff0bde149f6af8233f31aa08c1d41d4590eb50ce4b189192/vidyut-0.4.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b87107f21f924da74e2472d0cd4982d737dc77315a7fe192cd9a4df198b1515", size = 18636245, upload-time = "2025-01-22T07:26:51.119Z" }, - { url = "https://files.pythonhosted.org/packages/f8/f7/af88b4b818d22d040ff54e3e5f70b640b6ec99d6bb7eb34f66a9f0e545dc/vidyut-0.4.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b5bed1a4bf48211e0609c7e383d3165aa6a316ee75b3f9467f42189e5beb31a4", size = 23876066, upload-time = "2025-01-22T07:26:59.548Z" }, - { url = "https://files.pythonhosted.org/packages/91/16/c36e045941850ca6379349aa19f1acf9e7f6a271e7b5fa896a3602bdb0e1/vidyut-0.4.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cd81e600ee8c686619c7d6afa555a93f09badbd0724067e44488eec75771607", size = 19113361, upload-time = "2025-01-22T07:27:16.69Z" }, - { url = "https://files.pythonhosted.org/packages/0c/8a/87c3cf5a0ce4925d70bbdd1d65d0aaaf34ea0f2cd0c560e834befc8352fd/vidyut-0.4.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe7dfbfd1c48ddea6d5f74665dad9b9e7d5322d43aaad982f7a25c4dd9e58783", size = 18259051, upload-time = "2025-01-22T07:27:08.253Z" }, - { url = "https://files.pythonhosted.org/packages/f9/ee/472ed3dd517279dc4b5271383f37aea7f30eb2fddeceb53cf87e4f1f4a04/vidyut-0.4.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:990c5665a73da5156a7b2f78d425d47977938486f30eba3b6fab216c3611eb54", size = 18630041, upload-time = "2025-01-22T07:27:31.817Z" }, - { url = "https://files.pythonhosted.org/packages/c2/b2/5cfd46f557cf53e5ab562d0095008ddad9c55f01fd6be3987546856c2567/vidyut-0.4.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:6a5561e74ee614482c57489de0aa794601398e827a1c2674536c694d1f7668af", size = 18109898, upload-time = "2025-01-22T07:27:38.771Z" }, - { url = "https://files.pythonhosted.org/packages/8e/ad/e22478bcbe41f5606e22a3c0d5fc10a2c77a887c677bb95090e25ade605c/vidyut-0.4.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d82ab6e16b03dbd9c1e9c30341cc3dd4b5f866d0f3f8b8b9f4a56086db35808e", size = 18114763, upload-time = "2025-01-22T07:27:48.202Z" }, - { url = "https://files.pythonhosted.org/packages/bd/ae/e326679ec630ca927c96fdedad77378e0092c6d48a328119200d1ed61b31/vidyut-0.4.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a60556987be9957082c2432bfc36ab2615bed53f4c70c1591b552e721195c03b", size = 19128726, upload-time = "2025-01-22T07:27:54.81Z" }, - { url = "https://files.pythonhosted.org/packages/67/f5/01d5731f0fa8e6f80e84e7b151cd7c337f16b6e0f448cff040cce2b9a411/vidyut-0.4.0-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:26538146d6c2ac251bebf4133d90f867b3fb2c4c8a9a3795dd5763852c5dc239", size = 2537951, upload-time = "2025-01-22T07:27:28.521Z" }, - { url = "https://files.pythonhosted.org/packages/8d/7f/290ddcdeaa2b247c9a38e36644346ce5e7749362915d184efc428c8a4551/vidyut-0.4.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:e8f79aeb02c2be934d158b1d620260f95ca6f683707b73155b346804d58924a2", size = 2460025, upload-time = "2025-01-22T07:27:24.297Z" }, - { url = "https://files.pythonhosted.org/packages/46/22/8ad1faee4ef357d00ac00f2d6e15074d102e808d0a0236bc8297f98d675b/vidyut-0.4.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea709c0f5abeaa8edfa6f6eaa53ee656fd2c11c604e7fd237fe5d9a85c6eae4b", size = 18448000, upload-time = "2025-01-22T07:26:39.501Z" }, - { url = "https://files.pythonhosted.org/packages/53/06/ab1dda2c6c4f6094acd66579614b2b5c0d9c11013bf599694df58c8a2e12/vidyut-0.4.0-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b4ba80337bf9293ee7c8f2d9cc3cee16d6fa769391c5f492d339884ade0ad4e", size = 17713239, upload-time = "2025-01-22T07:26:47.314Z" }, - { url = "https://files.pythonhosted.org/packages/fd/d4/26d9775e1e5e7b120515957987d435559f15e52aee51055008b39cfe950e/vidyut-0.4.0-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a817fa7c2065ad9f764c79bfbeb84da8bcd74a5b451591cddc1715fc3e43ad6", size = 18504489, upload-time = "2025-01-22T07:26:54.98Z" }, - { url = "https://files.pythonhosted.org/packages/d9/c0/14f07e153690f043edbfbc0f5b8970b1a07b0d50a5c7839440760b148fda/vidyut-0.4.0-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3e6bec7f4d5d601b055242898274d2f5506166160dcf3130faf24138f902a9c0", size = 23855627, upload-time = "2025-01-22T07:27:03.876Z" }, - { url = "https://files.pythonhosted.org/packages/10/0f/62cb7aea1bddadc989854ca0b548466b0d55383b46c0247431b775334436/vidyut-0.4.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:844d213d283218708e4aa5723d656e270a0b639e6ab9af3af96c62b760868b1f", size = 18972888, upload-time = "2025-01-22T07:27:19.894Z" }, - { url = "https://files.pythonhosted.org/packages/fd/a1/8bbab20dcb4d72270be7a8f4a58291d42b85ad697a79ca426ab2e4d43e14/vidyut-0.4.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:234fd0afe1e2ae951c1dcc8476730df101b8b7d5b41607f1835170400e2a0ec7", size = 18135397, upload-time = "2025-01-22T07:27:12.812Z" }, - { url = "https://files.pythonhosted.org/packages/1c/5d/a2353ad30ff232a39d346a280e0cd7aa6812d6361c90193b02bda2320131/vidyut-0.4.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f8c4fbbdf7da16f57995e9b88ba92665cc941babd2233ddd4ffdb2a38de8513b", size = 18489174, upload-time = "2025-01-22T07:27:35.174Z" }, - { url = "https://files.pythonhosted.org/packages/fa/11/6328d14e87207bd23729fdd3ce5f8dea448d66c43fdb36abff7ffefab605/vidyut-0.4.0-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:d1d36ea4bdca42f8faafe57faab832052499f32975ce1cafaf042f56a9053690", size = 17987388, upload-time = "2025-01-22T07:27:44.511Z" }, - { url = "https://files.pythonhosted.org/packages/58/d7/96d1512b18ca68203a0b8c87b6056e2f16d2f9410d60829bc3bba94b715a/vidyut-0.4.0-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:1dbc097ad23a75c951f35cc9687b43bc73f9bb7cd5d1825d3f48247f2963219e", size = 18002635, upload-time = "2025-01-22T07:27:51.52Z" }, - { url = "https://files.pythonhosted.org/packages/91/0b/3e81e8447495b7b0a4e441c024cff55cc6a1f739787f72f3b8d3bd436be1/vidyut-0.4.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:07427a0a9b29e86d9ae92972ef4652f657c018cfd5fe761652d71c569ab39212", size = 18982852, upload-time = "2025-01-22T07:27:59.021Z" }, - { url = "https://files.pythonhosted.org/packages/2d/f0/0730b592ed5623a149ff365d9b02df078e6282ac8f5c2137d116f6ddccfe/vidyut-0.4.0-cp37-abi3-win32.whl", hash = "sha256:a60e512b58970b75f4415d7782724f7294a4e12568de373ad5bf289d0f43def1", size = 1963786, upload-time = "2025-01-22T07:28:04.746Z" }, - { url = "https://files.pythonhosted.org/packages/b2/93/72e17f386de3fdd416063de14a900c922cfcc5e38a6d829eb434f96dcb5c/vidyut-0.4.0-cp37-abi3-win_amd64.whl", hash = "sha256:15854f9a4cddaea0539bd02e727456946db4f39b417f83af2f9a8c9db03f1e52", size = 2103011, upload-time = "2025-01-22T07:28:03.248Z" }, -] - [[package]] name = "wrapt" version = "2.1.2" From 97ee06cbf6ba89906d7318a7d489acd32be013fa Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Mon, 25 May 2026 16:37:49 -0400 Subject: [PATCH 25/50] transform clips --- nirukta/timelines/recitation.py | 45 ++++++++------------------------- nirukta/timelines/sutra_file.py | 45 ++++++++++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 36 deletions(-) diff --git a/nirukta/timelines/recitation.py b/nirukta/timelines/recitation.py index a735076..a1af001 100644 --- a/nirukta/timelines/recitation.py +++ b/nirukta/timelines/recitation.py @@ -1,16 +1,15 @@ from dataclasses import dataclass +import sys from janim.imports import ( - ORANGE, - ORIGIN, + Aligned, + Config, FadeIn, FadeOut, Group, - RectClip, Succession, Timeline, Transform, - Vect, Wait, ) from nirukta.models import Sloka @@ -18,65 +17,43 @@ from nirukta.timelines import LenientTransformMatchingDiff -scaledown = 0.5 - - -def place_in_corner(clip: RectClip, corner: Vect): - # TODO: fiddle w this - clip.transform.set(scale=scaledown * 1.2) - clip.points.scale(scaledown) - clip.points.to_border(corner, buff=0) - - @dataclass class RecitationTimeline(Timeline): sloka: Sloka devanagari: bool chandas: bool - corner: Vect - def __init__(self, sloka: Sloka, devanagari: bool, chandas: bool, corner: Vect): + def __init__(self, sloka: Sloka, devanagari: bool, chandas: bool): super().__init__() self.sloka = sloka self.devanagari = devanagari self.chandas = chandas - self.corner = corner def construct(self): group = sloka_group_reformed(self.sloka, devanagari=self.devanagari) - group_clip = RectClip(group, anchor=ORIGIN, border=True) - place_in_corner(group_clip, self.corner) self.play( Succession( FadeIn(group), Wait(1.0), - # Aligned( - # left.anim.points.shift(LEFT * scaledown * 6), - # right.anim.points.shift(RIGHT * scaledown * 6), - # right.anim.points.scale(0.2) - # ), - Wait(1.0), - # FadeOut(Group(lt, left, rt, right)) ) ) if self.chandas: blank = sloka_group_chandas(self.sloka, blank=True, matras=False) chandas = sloka_group_chandas(self.sloka, blank=False, matras=False) - g = Group(blank.text, blank.keys, chandas.text, chandas.keys) - group_clip.apply(*g) self.play( Succession( LenientTransformMatchingDiff(group, blank.text, duration=0.5), - Transform(blank.text, chandas.text, duration=0.5), + Aligned( + Transform(blank.text, chandas.text), + FadeIn(chandas.keys), + duration=0.5, + ), Wait(1.0), - FadeIn(chandas.keys, duration=0.5), FadeOut( Group( - group, - group_clip, chandas.keys, chandas.text, ), @@ -85,6 +62,4 @@ def construct(self): ) ) else: - self.play( - Succession(Wait(2.5), FadeOut(Group(group, group_clip), duration=0.5)) - ) + self.play(Succession(Wait(2.0), FadeOut(group, duration=0.5))) diff --git a/nirukta/timelines/sutra_file.py b/nirukta/timelines/sutra_file.py index cf60614..224dd9d 100644 --- a/nirukta/timelines/sutra_file.py +++ b/nirukta/timelines/sutra_file.py @@ -26,6 +26,7 @@ Text, Timeline, Transform, + TransformableFrameClip, TypstText, Wait, Write, @@ -52,6 +53,7 @@ sloka_thumbnail, ) from nirukta.timelines.explain_sloka import ExplainSloka, build_explain_sloka_cached +from nirukta.timelines.recitation import RecitationTimeline @dataclass @@ -110,8 +112,49 @@ def construct(self): # ) # title.points.move_to(UL) # self.play(FadeIn(title)) + # clip_h = 0.25 + # clip_v = 0.25 + # offset_x = -clip_h + col * (1 / cols) # -0.25 or +0.25 + # offset_y = clip_v - row * (1 / rows) # 0.25 or -0.25 for sloka in self.slokas: + listen_deva = ( + RecitationTimeline(sloka=sloka, devanagari=True, chandas=False) + .build() + .to_item() + .show() + ) + listen_iast = ( + RecitationTimeline(sloka=sloka, devanagari=False, chandas=False) + .build() + .to_item() + .show() + ) + recitation = ( + RecitationTimeline(sloka=sloka, devanagari=True, chandas=True) + .build() + .to_item() + .show() + ) + TransformableFrameClip( + listen_deva, + offset=(-0.25, 0.25), + scale=0.5, + ).show() + TransformableFrameClip( + listen_iast, + offset=(+0.25, 0.25), + scale=0.5, + ).show() + TransformableFrameClip( + recitation, + offset=(0, -0.25), + scale=0.5, + ).show() + self.forward_to(listen_deva.end) + # self.fade + + """ for sloka in self.slokas: # introduction = IntroduceSloka(sloka).build().to_item() # left = sloka_group(sloka) @@ -255,7 +298,7 @@ def place_in_corner(clip, corner): ), ), duration=1.0, - ) + ) """ # self.play(FadeOut(title)) From fa4c2bc020edf65a10f2d551bf882cf263fd904c Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Tue, 26 May 2026 10:42:48 -0400 Subject: [PATCH 26/50] more subtimelines --- library/sUtrARi/vakratuRqa_mahAkAya.sutra | 13 ++++ main.py | 3 +- nirukta/models/presentation/sloka.py | 32 ++++++-- nirukta/render.py | 10 +-- nirukta/sloka.py | 56 +++++++------- nirukta/timelines/english.py | 56 ++++++++++++++ nirukta/timelines/explain_sloka.py | 63 +++++++++------ nirukta/timelines/introduce_sloka.py | 11 ++- nirukta/timelines/sutra_file.py | 23 ++++-- nirukta/timelines/thumbnail.py | 94 +++++++++++++++++++++++ 10 files changed, 290 insertions(+), 71 deletions(-) create mode 100644 library/sUtrARi/vakratuRqa_mahAkAya.sutra create mode 100644 nirukta/timelines/english.py create mode 100644 nirukta/timelines/thumbnail.py diff --git a/library/sUtrARi/vakratuRqa_mahAkAya.sutra b/library/sUtrARi/vakratuRqa_mahAkAya.sutra new file mode 100644 index 0000000..738f274 --- /dev/null +++ b/library/sUtrARi/vakratuRqa_mahAkAya.sutra @@ -0,0 +1,13 @@ +=== meow === + +=== sloka === + +--- line --- +vakra[curved]+tuRqa[trunk]=vakratuRqa mahA[large]+kAya[body]=mahAkAya sUrya[suns]+kowi[10,000,000]=sUryakowi sama[equal to]+praBa[brilliance]=samapraBa . +"O you with the curved trunk, the large body," +"and the brilliance equal to 10,000,000 suns..." + +--- line --- +(nir[free of]+viGnam[obstacles]=nirviGnam)=nirviGnaM kuru[make] me[for me] deva[god] sarva[all] kAryezu[in][endeavors] sarva[all]+dA[times]=sarvadA .. +"O god - make the way free of obstacles for me," +"in all endeavors and all times." diff --git a/main.py b/main.py index 389bf32..0336fb2 100644 --- a/main.py +++ b/main.py @@ -20,6 +20,7 @@ # `janim write` (export) keeps full 1920×1080. _preview_mode = "run" in sys.argv + class Nirukta(Timeline): CONFIG = Config( fps=60, @@ -38,4 +39,4 @@ def gui_color(self) -> str: def construct(self): assert is_nirukta_file(chosen), "Invalid file" timeline = file_to_timeline(chosen).build().to_item().show() - self.forward_to(timeline.end) + self.forward(timeline.duration) diff --git a/nirukta/models/presentation/sloka.py b/nirukta/models/presentation/sloka.py index ba0d5d6..a0c57bc 100644 --- a/nirukta/models/presentation/sloka.py +++ b/nirukta/models/presentation/sloka.py @@ -50,8 +50,8 @@ def meter(self) -> tuple[str, List[List[Akshara]]]: padas: List[List[Akshara]] = [] for li in range(len(text_lines)): line_sounds = list(filter(lambda x: len(x) > 0, text_lines[li].split(" "))) - print(line_sounds) - print(weight_lines[li]) + # print(line_sounds) + # print(weight_lines[li]) aksharas: List[Akshara] = [] for si in range(len(line_sounds)): @@ -60,9 +60,9 @@ def meter(self) -> tuple[str, List[List[Akshara]]]: ) padas.append(aksharas) - print(verse.meter_label) - print(verse.morae_per_line) - print(padas) + # print(verse.meter_label) + # print(verse.morae_per_line) + # print(padas) label = transliterate.process("IAST", "SLP1", verse.meter_label) assert label is not None @@ -71,3 +71,25 @@ def meter(self) -> tuple[str, List[List[Akshara]]]: label = label.split(" ")[0].strip() return (label, padas) + + def english_typst(self) -> List[str]: + english = [] + for line in self.lines: + el = "" + for vAkya in line.vAkyAni: + el += vAkya.english + "\n" + english.append(el) + + return english + + # rows = [] + # + # for line in self.lines: + # english = "" + # for vAkya in line.vAkyAni: + # english += vAkya.english + "#linebreak()" + # + # rows.append(typst_code(english, Language.ENGLISH)) + # + # return + # return arrange_vertical(rows, gutter=0.6) diff --git a/nirukta/render.py b/nirukta/render.py index c738cda..4a9bc92 100644 --- a/nirukta/render.py +++ b/nirukta/render.py @@ -252,8 +252,8 @@ def typst_code( return text_box(transformed, color, stroke_mode) -def scale_with_stroke(group: Group, factor: float) -> Group: - group.points.scale(factor) - for item in group.walk_descendants(VItem): - item.radius.set(item.radius.get() * factor) - return group +# def scale_with_stroke(group: Group, factor: float) -> Group: +# group.points.scale(factor) +# for item in group.walk_descendants(VItem): +# item.radius.set(item.radius.get() * factor) +# return group diff --git a/nirukta/sloka.py b/nirukta/sloka.py index 1aec021..c516400 100644 --- a/nirukta/sloka.py +++ b/nirukta/sloka.py @@ -18,7 +18,7 @@ from nirukta.models import Language, Sloka from janim.imports import WHITE -from nirukta.render import scale_with_stroke, set_font, transform_text, typst_code +from nirukta.render import set_font, transform_text, typst_code from typing import List from nirukta.typst import arrange_vertical, box_cell, arrange_horizontal @@ -58,7 +58,25 @@ """ -def sloka_group_reformed(sloka: Sloka, devanagari: bool = True) -> TypstText: +def sloka_group_english(sloka: Sloka) -> TypstText: + rows = [] + + for line in sloka.lines: + english = "" + for vAkya in line.vAkyAni: + english += vAkya.english + "#linebreak()" + + rows.append(typst_code(english, Language.ENGLISH)) + + grid = arrange_vertical(rows, gutter=0.6) + + return TypstText( + set_font(grid, LATIN_FONT), + scale=SCALE, + ) + + +def sloka_group_reformed(sloka: Sloka, devanagari: bool) -> TypstText: rows = [] if devanagari: @@ -84,11 +102,9 @@ def sloka_group_reformed(sloka: Sloka, devanagari: bool = True) -> TypstText: ) sanskritcode += utterance_code + " " - sanskritcode = f"[{sanskritcode}]" - rows.append(sanskritcode) + rows.append(f"[{sanskritcode}]") grid = arrange_vertical(rows, gutter=0.6) - print(f"\n\ngrid:\n{grid}\n") return TypstText( set_font(grid, font), @@ -187,28 +203,8 @@ def title_and_pada_labels( return Group(title, *labelz) -def sloka_group_english(sloka: Sloka) -> Group[TypstText]: - group = [] - - for li, line in enumerate(sloka.lines): - english = "" - for vi, vAkya in enumerate(line.vAkyAni): - english += vAkya.english + "#linebreak()" - - group.append( - TypstText( - set_font(typst_code(english, Language.ENGLISH), LATIN_FONT), - scale=SCALE, - ) - ) - - group = Group(*group) - group.points.arrange(DOWN) - return group - - -def sloka_thumbnail(sloka: Sloka) -> Group: - sloka_text = sloka_group_reformed(sloka) +""" def sloka_thumbnail(sloka: Sloka) -> Group: + sloka_text = sloka_group_reformed(sloka, devanagari=True) if sloka.number is not None: number_label = Group( Rect(0.4, 0.4, fill_alpha=0.3), @@ -221,10 +217,10 @@ def sloka_thumbnail(sloka: Sloka) -> Group: Group(sloka_text, number_label), color=WHITE, buff=MED_SMALL_BUFF ) - group = scale_with_stroke(Group(sloka_text, sloka_border, number_label), 0.5) + group = Group(sloka_text, sloka_border, number_label) else: sloka_border = SurroundingRect(sloka_text, color=WHITE, buff=MED_SMALL_BUFF) - group = scale_with_stroke(Group(sloka_text, sloka_border), 0.5) + group = Group(sloka_text, sloka_border) group.points.to_border(UL, buff=MED_SMALL_BUFF) - return group + return group """ diff --git a/nirukta/timelines/english.py b/nirukta/timelines/english.py new file mode 100644 index 0000000..8ec6a76 --- /dev/null +++ b/nirukta/timelines/english.py @@ -0,0 +1,56 @@ +from dataclasses import dataclass + +from janim.imports import ( + FadeOut, + Succession, + Timeline, + TypstText, + Wait, + Write, +) +from nirukta.constants import LATIN_FONT +from nirukta.models import Language, Sloka +from nirukta.render import set_font, typst_code +from nirukta.typst import arrange_vertical +from nirukta.util import SCALE + + +@dataclass +class EnglishTimeline(Timeline): + sloka: Sloka + + def __init__(self, sloka: Sloka): + super().__init__() + self.sloka = sloka + + def construct(self): + # group = sloka_group_english(self.sloka) + + rows = [] + + for line in self.sloka.lines: + # english = "" + for vAkya in line.vAkyAni: + # vAkya.tokens.def + # english += vAkya.english + code = typst_code(vAkya.english, Language.ENGLISH) + rows.append(f"[{code}]") + + emptyrow = typst_code("", Language.ENGLISH) + rows.append(f"[{emptyrow}]") + + print(f"there are {len(rows)} rows in the english text") + print(f"{rows}") + + grid = arrange_vertical(rows, gutter=0.6) + + group = TypstText( + set_font(grid, LATIN_FONT), + scale=SCALE, + ) + + self.play( + Succession( + Write(group, duration=2.0), Wait(2.0), FadeOut(group, duration=0.5) + ) + ) diff --git a/nirukta/timelines/explain_sloka.py b/nirukta/timelines/explain_sloka.py index 9373b35..18ea724 100644 --- a/nirukta/timelines/explain_sloka.py +++ b/nirukta/timelines/explain_sloka.py @@ -2,17 +2,26 @@ import dill as pickle from typing import Any, List -from janim.imports import YELLOW, Aligned, FadeIn, FadeOut, Succession, Timeline, Write +from janim.imports import ( + YELLOW, + Aligned, + FadeIn, + FadeOut, + Succession, + Timeline, + Write, + TransformableFrameClip, +) from janim.logger import log from nirukta.models import Line, Sloka from nirukta.render import Awaken, Sleep -from nirukta.sloka import sloka_thumbnail from nirukta.timelines import ( LenientTransformMatchingDiff, UtteranceTimeline, build_utterance_cached, ) from nirukta.timelines.line import LineTimeline +from nirukta.timelines.thumbnail import ThumbnailTimeline # Memory-only cache: disk caching is handled at the utterance level. _built_cache: dict[str, Any] = {} @@ -41,23 +50,33 @@ def gui_color(self) -> str: return YELLOW def construct(self): - thumbnail = sloka_thumbnail(self.sloka) - # initial = sloka_group(self.sloka) - # self.play(Write(initial), duration=0.33) - # self.play(LenientTransformMatchingDiff(initial, thumbnail[0]), duration=0.33) - # self.play(Aligned(FadeIn(thumbnail[1:]), Sleep(thumbnail[0]))) - self.play(Aligned(FadeIn(thumbnail), Sleep(thumbnail[0]))) - - for li, line in enumerate(self.sloka.lines): - for vi, vAkya in enumerate(line.vAkyAni): - if li != 0 or vi != 0: - self.play(Sleep(thumbnail[0])) - - selection = thumbnail[0][li].get_label(f"line_{li}_utterance_{vi}") - self.play(Awaken(selection)) - - vt = build_utterance_cached(vAkya).to_item().show() - self.forward_to(vt.end) - - self.play(Sleep(thumbnail[0])) - self.play(FadeOut(thumbnail)) + thumb = ThumbnailTimeline(sloka=self.sloka).build().to_item().show() + + TransformableFrameClip( + thumb, + offset=(-0.25, 0.25), + scale=0.5, + ).show() + + self.forward_to(thumb.end) + + # thumbnail = sloka_thumbnail(self.sloka) + # # initial = sloka_group(self.sloka) + # # self.play(Write(initial), duration=0.33) + # # self.play(LenientTransformMatchingDiff(initial, thumbnail[0]), duration=0.33) + # # self.play(Aligned(FadeIn(thumbnail[1:]), Sleep(thumbnail[0]))) + # self.play(Aligned(FadeIn(thumbnail), Sleep(thumbnail[0]))) + # + # for li, line in enumerate(self.sloka.lines): + # for vi, vAkya in enumerate(line.vAkyAni): + # if li != 0 or vi != 0: + # self.play(Sleep(thumbnail[0])) + # + # selection = thumbnail[0][li].get_label(f"line_{li}_utterance_{vi}") + # self.play(Awaken(selection)) + # + # vt = build_utterance_cached(vAkya).to_item().show() + # self.forward_to(vt.end) + # + # self.play(Sleep(thumbnail[0])) + # self.play(FadeOut(thumbnail)) diff --git a/nirukta/timelines/introduce_sloka.py b/nirukta/timelines/introduce_sloka.py index 295c814..1f328c2 100644 --- a/nirukta/timelines/introduce_sloka.py +++ b/nirukta/timelines/introduce_sloka.py @@ -46,10 +46,12 @@ def gui_color(self) -> str: return YELLOW def construct(self): - sloka_g = sloka_group_reformed(self.sloka) + sloka_g = sloka_group_reformed(self.sloka, devanagari=True) - for line in sloka_g: - self.play(Write(line, duration=4.0)) + # for line in sloka_g: + self.play(Write(sloka_g, duration=4.0)) + + self.play(Wait(1.0)) # Move glyphs into grid boxes sloka_chandas_blank = sloka_group_chandas(self.sloka, blank=True) @@ -60,6 +62,7 @@ def construct(self): sloka_g, sloka_chandas_blank.text, duration=0.5 ) ) + self.play(Wait(1.0)) # Reveal the prosodic colors @@ -68,6 +71,8 @@ def construct(self): # Reveal keys self.play(FadeIn(sloka_chandas.keys)) + self.play(Wait(2.0)) + # Expand boxes by vowel duration sloka_matras = sloka_group_chandas(self.sloka, matras=True) diff --git a/nirukta/timelines/sutra_file.py b/nirukta/timelines/sutra_file.py index 224dd9d..493eeb4 100644 --- a/nirukta/timelines/sutra_file.py +++ b/nirukta/timelines/sutra_file.py @@ -41,7 +41,6 @@ from nirukta.render import ( Awaken, Sleep, - scale_with_stroke, transform_text, set_font, typst_code, @@ -50,8 +49,8 @@ sloka_group_chandas, sloka_group_english, sloka_group_reformed, - sloka_thumbnail, ) +from nirukta.timelines.english import EnglishTimeline from nirukta.timelines.explain_sloka import ExplainSloka, build_explain_sloka_cached from nirukta.timelines.recitation import RecitationTimeline @@ -136,6 +135,7 @@ def construct(self): .to_item() .show() ) + translation = EnglishTimeline(sloka=sloka).build().to_item().show() TransformableFrameClip( listen_deva, offset=(-0.25, 0.25), @@ -148,11 +148,24 @@ def construct(self): ).show() TransformableFrameClip( recitation, - offset=(0, -0.25), + offset=(-0.25, -0.25), scale=0.5, ).show() - self.forward_to(listen_deva.end) - # self.fade + TransformableFrameClip( + translation, + offset=(+0.25, -0.25), + scale=0.5, + ).show() + + self.forward( + max( + listen_deva.duration, + listen_iast.duration, + recitation.duration, + translation.duration, + ) + ) + # self.prepare(FadeOut(translation), duration=1.0) """ for sloka in self.slokas: # introduction = IntroduceSloka(sloka).build().to_item() diff --git a/nirukta/timelines/thumbnail.py b/nirukta/timelines/thumbnail.py new file mode 100644 index 0000000..4bb35f0 --- /dev/null +++ b/nirukta/timelines/thumbnail.py @@ -0,0 +1,94 @@ +import hashlib +import dill as pickle +from typing import Any, List + +from janim.imports import ( + LEFT, + MED_SMALL_BUFF, + UL, + UP, + WHITE, + YELLOW, + Aligned, + FadeIn, + FadeOut, + Group, + Rect, + Succession, + SurroundingRect, + Text, + Timeline, + Write, +) +from janim.logger import log +from nirukta.models import Line, Sloka +from nirukta.render import Awaken, Sleep +from nirukta.sloka import sloka_group_reformed +from nirukta.timelines import ( + LenientTransformMatchingDiff, + UtteranceTimeline, + build_utterance_cached, +) +from nirukta.timelines.line import LineTimeline + +# Memory-only cache: disk caching is handled at the utterance level. +_built_cache: dict[str, Any] = {} + + +class ThumbnailTimeline(Timeline): + sloka: Sloka + + def __init__(self, sloka: Sloka): + super().__init__() + self.sloka = sloka + + @property + def gui_color(self) -> str: + return YELLOW + + def construct(self): + # thumbnail = sloka_thumbnail(self.sloka) + + sloka_text = sloka_group_reformed(self.sloka, devanagari=True) + if self.sloka.number is not None: + number_label = Group( + Rect(0.4, 0.4, fill_alpha=0.3), + Text(f"{self.sloka.number}", font_size=22), + ) + number_label.points.next_to( + sloka_text, UP, buff=MED_SMALL_BUFF, aligned_edge=LEFT + ) + sloka_border = SurroundingRect( + Group(sloka_text, number_label), color=WHITE, buff=MED_SMALL_BUFF + ) + + group = Group(sloka_text, sloka_border, number_label) + else: + sloka_border = SurroundingRect(sloka_text, color=WHITE, buff=MED_SMALL_BUFF) + group = Group(sloka_text, sloka_border) + + group.points.to_border(UL, buff=MED_SMALL_BUFF) + # return group + # initial = sloka_group(self.sloka) + # self.play(Write(initial), duration=0.33) + # self.play(LenientTransformMatchingDiff(initial, thumbnail[0]), duration=0.33) + # self.play(Aligned(FadeIn(thumbnail[1:]), Sleep(thumbnail[0]))) + self.play(Aligned(FadeIn(group), Sleep(sloka_text))) + + print() + print(sloka_text.text) + print() + + for li, line in enumerate(self.sloka.lines): + for vi, vAkya in enumerate(line.vAkyAni): + if li != 0 or vi != 0: + self.play(Sleep(group)) + + selection = sloka_text.get_label(f"line_{li}_utterance_{vi}") + self.play(Awaken(selection)) + + vt = build_utterance_cached(vAkya).to_item() + self.forward(vt.duration) + + self.play(Sleep(sloka_text)) + self.play(FadeOut(group)) From eeaf3a9c25976c1a4ed5fe90853f1ba505bddada Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Tue, 26 May 2026 10:55:50 -0400 Subject: [PATCH 27/50] simplify logic + add borders --- demo.py | 4 ++-- nirukta/timelines/english.py | 14 ++++---------- nirukta/timelines/sutra_file.py | 22 ++++++++++++++++++++++ 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/demo.py b/demo.py index ecfd630..0e07714 100644 --- a/demo.py +++ b/demo.py @@ -91,8 +91,8 @@ def construct(self): ).show() center = ( - (col + 0.5 - cols / 2) * quad_w, - (rows / 2 - row - 0.5) * quad_h, + (col + 0.5 - 1.0) * quad_w, + (1.0 - row - 0.5) * quad_h, 0, ) border = Rect(quad_w, quad_h, color=WHITE, stroke_radius=0.03) diff --git a/nirukta/timelines/english.py b/nirukta/timelines/english.py index 8ec6a76..829c485 100644 --- a/nirukta/timelines/english.py +++ b/nirukta/timelines/english.py @@ -24,25 +24,19 @@ def __init__(self, sloka: Sloka): self.sloka = sloka def construct(self): - # group = sloka_group_english(self.sloka) - rows = [] for line in self.sloka.lines: - # english = "" for vAkya in line.vAkyAni: - # vAkya.tokens.def - # english += vAkya.english - code = typst_code(vAkya.english, Language.ENGLISH) - rows.append(f"[{code}]") + rows.append(typst_code(vAkya.english, Language.ENGLISH)) - emptyrow = typst_code("", Language.ENGLISH) - rows.append(f"[{emptyrow}]") + # Add an empty row between lines + rows.append(typst_code("", Language.ENGLISH)) print(f"there are {len(rows)} rows in the english text") print(f"{rows}") - grid = arrange_vertical(rows, gutter=0.6) + grid = arrange_vertical(list(map(lambda code: f"[{code}]", rows)), gutter=0.6) group = TypstText( set_font(grid, LATIN_FONT), diff --git a/nirukta/timelines/sutra_file.py b/nirukta/timelines/sutra_file.py index 493eeb4..2be5f26 100644 --- a/nirukta/timelines/sutra_file.py +++ b/nirukta/timelines/sutra_file.py @@ -2,6 +2,7 @@ from typing import List, Optional from janim.imports import ( + Config, LEFT, MED_SMALL_BUFF, ORANGE, @@ -115,6 +116,25 @@ def construct(self): # clip_v = 0.25 # offset_x = -clip_h + col * (1 / cols) # -0.25 or +0.25 # offset_y = clip_v - row * (1 / rows) # 0.25 or -0.25 + fw = Config.get.frame_width + fh = Config.get.frame_height + quad_w = fw / 2 + quad_h = fh / 2 + + borders = [] + for i in range(4): + col = i % 2 + row = i // 2 + center = ( + (col + 0.5 - 1.0) * quad_w, + (1.0 - row - 0.5) * quad_h, + 0, + ) + + border = Rect(quad_w, quad_h, color=WHITE, stroke_radius=0.03) + border.points.move_to(center) + borders.append(border) + self.play(FadeIn(Group(*borders))) for sloka in self.slokas: listen_deva = ( @@ -136,6 +156,7 @@ def construct(self): .show() ) translation = EnglishTimeline(sloka=sloka).build().to_item().show() + TransformableFrameClip( listen_deva, offset=(-0.25, 0.25), @@ -167,6 +188,7 @@ def construct(self): ) # self.prepare(FadeOut(translation), duration=1.0) + self.play(FadeOut(Group(*borders))) """ for sloka in self.slokas: # introduction = IntroduceSloka(sloka).build().to_item() From bc231c93aaa8c31da5733b73705cfe8adff6ffc7 Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Wed, 27 May 2026 10:59:17 -0400 Subject: [PATCH 28/50] visualizing longer chandas --- library/guru_gIta.sloka | 18 ++++++++++++++++++ nirukta/render.py | 2 +- nirukta/sloka.py | 10 ++-------- nirukta/typst.py | 12 +++++++++--- 4 files changed, 30 insertions(+), 12 deletions(-) create mode 100644 library/guru_gIta.sloka diff --git a/library/guru_gIta.sloka b/library/guru_gIta.sloka new file mode 100644 index 0000000..d269831 --- /dev/null +++ b/library/guru_gIta.sloka @@ -0,0 +1,18 @@ +=== sloka === + + +--- line --- +brahmAnandaM[x] paramasuKadaM kevalaM jYAnamUrtiM +"x" + +--- line --- +dvandvAtItaM[x] gaganadadfSaM tattvamasyAdilakzyam . +"x" + +--- line --- +ekaM[x] nityaM vimalamacalaM sarvaDIsAkziBUtaM +"x" + +--- line --- +BAvAtItaM[x] triguRarahitaM sadguruM taM namAmi .. +"x" diff --git a/nirukta/render.py b/nirukta/render.py index 4a9bc92..4e9a179 100644 --- a/nirukta/render.py +++ b/nirukta/render.py @@ -197,7 +197,7 @@ def T(s): def set_font(text: str, font: str): return ( - f'#set text(font: "{font}", stroke: none)\n' + f'#set text(font: "{font}", size: 6pt, stroke: none)\n' # f"#set page(width: {266 * SCALE}pt)\n" f"{text}" ) diff --git a/nirukta/sloka.py b/nirukta/sloka.py index c516400..33c3e3f 100644 --- a/nirukta/sloka.py +++ b/nirukta/sloka.py @@ -131,8 +131,6 @@ def sloka_group_chandas( lang = Language.TRANSLIT font = LATIN_FONT - base_width = 1.8 - gutter = 0.2 all_cells = [] cell_idx = 0 @@ -146,11 +144,7 @@ def sloka_group_chandas( all_cells.append( box_cell( content=deva, - width=( - base_width * 2 + gutter - if (matras and akshara.is_long()) - else base_width - ), + wide=matras and akshara.is_long(), idx=cell_idx, fill=fill, ) @@ -169,7 +163,7 @@ def sloka_group_chandas( row_label = f"row_{idx}" row_labels.append(row_label) - grid_code = f"#grid(rows: (auto,) * {n}, gutter: {gutter}em, {', '.join(rows)})" + grid_code = arrange_vertical(rows) grid = TypstText(set_font(grid_code, font), scale=SCALE) if blank: diff --git a/nirukta/typst.py b/nirukta/typst.py index cf4b749..5591161 100644 --- a/nirukta/typst.py +++ b/nirukta/typst.py @@ -1,13 +1,13 @@ from typing import List, Optional -cell_width = 1.6 +height = 1.8 gutter = 0.2 def box_cell( content: str, - width: Optional[float] = cell_width, + wide: bool, idx: Optional[int] = None, fill: Optional[str] = None, ) -> str: @@ -15,8 +15,14 @@ def box_cell( if fill is None: fill = "rgb(0, 0, 0, 0)" + width=( + height * 2 + gutter + if wide + else height + ) + return ( - f"[#box(fill: {fill}, width: {width}em, height: {cell_width}em, radius: 0.4em)" + f"[#box(fill: {fill}, width: {width}em, height: {height}em, radius: 0.4em)" f"[#align(center + horizon)[#text(fill: white)[{content}]]]" f"{'' if idx is None else f''}]" ) From 085eab0a08620d857e7fbb64cafa61bca38b439f Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Thu, 28 May 2026 11:40:42 -0400 Subject: [PATCH 29/50] aliases in flake for direnv --- flake.nix | 74 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 46 insertions(+), 28 deletions(-) diff --git a/flake.nix b/flake.nix index 1af556d..242d7e0 100644 --- a/flake.nix +++ b/flake.nix @@ -1,5 +1,5 @@ { - description = "Dev shell for JAnim + PySide6 on Wayland"; + description = "Dev shell for JAnim PySide6 on Wayland"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; @@ -16,42 +16,60 @@ system: let pkgs = import nixpkgs { inherit system; }; + + # Function to create script + mkScript = + name: text: + let + script = pkgs.writeShellScriptBin name text; + in + script; + + # Define your scripts/aliases + scripts = [ + (mkScript "janim" ''uv run janim "$@"'') + (mkScript "nirukta" ''janim run main.py "$@"'') + (mkScript "nvim" ''uv run ${pkgs.neovim}/bin/nvim "$@"'') + ]; # Packages that need to be included in the runtime path - runtimeLibs = with pkgs; [ - python313 + runtimeLibs = + with pkgs; + [ + python313 - zstd - zlib + zstd + zlib - # OpenGL - mesa - libGL - libGLU - glib - fontconfig + # OpenGL + mesa + libGL + libGLU + glib + fontconfig - # Keymap handling - libxkbcommon + # Keymap handling + libxkbcommon - # X11 (still needed even on Wayland for XWayland fallback) - libx11 - libxext - libxcb + # X11 (still needed even on Wayland for XWayland fallback) + libx11 + libxext + libxcb - # Wayland - wayland - wayland-protocols + # Wayland + wayland + wayland-protocols - # Audio - alsa-lib + # Audio + alsa-lib - # Fonts / display - freetype + # Fonts / display + freetype - # System - dbus - stdenv.cc.cc.lib - ]; + # System + dbus + stdenv.cc.cc.lib + ] + ++ scripts; in { devShells.default = pkgs.mkShell { From 74bf0b396bb5832ba95271da3c27f77fd751f41b Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Thu, 28 May 2026 11:40:53 -0400 Subject: [PATCH 30/50] quadrants timeline --- nirukta/timelines/quadrants.py | 72 +++++++ nirukta/timelines/sutra_file.py | 321 ++------------------------------ 2 files changed, 87 insertions(+), 306 deletions(-) create mode 100644 nirukta/timelines/quadrants.py diff --git a/nirukta/timelines/quadrants.py b/nirukta/timelines/quadrants.py new file mode 100644 index 0000000..b6a226f --- /dev/null +++ b/nirukta/timelines/quadrants.py @@ -0,0 +1,72 @@ +from dataclasses import dataclass +from typing import List + +from janim.imports import ( + BLUE, + Config, + WHITE, + FadeIn, + FadeOut, + Group, + Rect, + Timeline, + TransformableFrameClip, +) + + +@dataclass +class QuadrantsTimeline(Timeline): + timelines: List[Timeline] + + def __init__(self, timelines: List[Timeline]): + # Exit early if args are invalid + if len(timelines) > 4 or len(timelines) < 1: + raise ValueError(f"Cannot display ${len(timelines)} in a quadrant layout.") + + super().__init__() + self.timelines = timelines + + @property + def gui_name(self) -> str: + return "Quadrants" + + @property + def gui_color(self) -> str: + return BLUE + + def construct(self): + fw = Config.get.frame_width + fh = Config.get.frame_height + quad_w = fw / 2 + quad_h = fh / 2 + + borders = [] + for i in range(4): + col = i % 2 + row = i // 2 + center = ( + (col + 0.5 - 1.0) * quad_w, + (1.0 - row - 0.5) * quad_h, + 0, + ) + + border = Rect(quad_w, quad_h, color=WHITE, stroke_radius=0.03) + border.points.move_to(center) + borders.append(border) + self.play(FadeIn(Group(*borders))) + + # for sloka in self.timelines: + offsets = [(-0.25, +0.25), (+0.25, +0.25), (-0.25, -0.25), (+0.25, -0.25)] + durations = [] + + for idx, timeline in enumerate(self.timelines): + built_timeline = timeline.build().to_item().show() + TransformableFrameClip( + built_timeline, offset=offsets[idx], scale=0.5 + ).show() + durations.append(built_timeline.duration) + + # Wait + self.forward(max(durations)) + + self.play(FadeOut(Group(*borders))) diff --git a/nirukta/timelines/sutra_file.py b/nirukta/timelines/sutra_file.py index 2be5f26..f62d380 100644 --- a/nirukta/timelines/sutra_file.py +++ b/nirukta/timelines/sutra_file.py @@ -1,58 +1,29 @@ from dataclasses import dataclass -from typing import List, Optional +from typing import List from janim.imports import ( Config, - LEFT, - MED_SMALL_BUFF, ORANGE, ORIGIN, - RIGHT, - UL, - DL, - UP, - DOWN, - UR, - DR, WHITE, - Aligned, FadeIn, FadeOut, Group, Rect, - RectClip, - ShrinkToEdge, - Succession, - SurroundingRect, - Text, Timeline, - Transform, TransformableFrameClip, TypstText, Wait, Write, ) -from nirukta.constants import INACTIVE, SANSKRIT_FONT, SCALE +from nirukta.constants import SANSKRIT_FONT, SCALE from nirukta.models import Language, Sloka, SutraFile -from nirukta.timelines import ( - IntroduceSloka, - LenientTransformMatchingDiff, - UtteranceTimeline, -) from nirukta.render import ( - Awaken, - Sleep, - transform_text, set_font, typst_code, ) -from nirukta.sloka import ( - sloka_group_chandas, - sloka_group_english, - sloka_group_reformed, -) from nirukta.timelines.english import EnglishTimeline -from nirukta.timelines.explain_sloka import ExplainSloka, build_explain_sloka_cached +from nirukta.timelines.quadrants import QuadrantsTimeline from nirukta.timelines.recitation import RecitationTimeline @@ -89,282 +60,20 @@ def construct(self): ]: self.play(animation) - # Listening side by side with pronunciation guide - # for sloka in self.slokas: - # sloka.meter() - # slp1 = sloka.slp1() - - # sloka. - # verse = MI.identify_meter(slp1) # from_scheme auto-detected; output IAST - # print(verse.meter_label) - # print(verse.summarize()) - # verse = MI.identify_meter(slp1, resplit_option='none') - # verse = MI.identify_meter(slp1, from_scheme='SLP', resplit_option='resplit_lite') - - listen = "SravaRa" - # recite = "pAWa" - # title = TypstText( - # set_font( - # f"#text(fill: white, size: 1.2em)[{transform_text(listen, Language.SANSKRIT)}]", - # SANSKRIT_FONT, - # ), - # scale=SCALE, - # ) - # title.points.move_to(UL) - # self.play(FadeIn(title)) - # clip_h = 0.25 - # clip_v = 0.25 - # offset_x = -clip_h + col * (1 / cols) # -0.25 or +0.25 - # offset_y = clip_v - row * (1 / rows) # 0.25 or -0.25 - fw = Config.get.frame_width - fh = Config.get.frame_height - quad_w = fw / 2 - quad_h = fh / 2 - - borders = [] - for i in range(4): - col = i % 2 - row = i // 2 - center = ( - (col + 0.5 - 1.0) * quad_w, - (1.0 - row - 0.5) * quad_h, - 0, - ) - - border = Rect(quad_w, quad_h, color=WHITE, stroke_radius=0.03) - border.points.move_to(center) - borders.append(border) - self.play(FadeIn(Group(*borders))) - for sloka in self.slokas: - listen_deva = ( - RecitationTimeline(sloka=sloka, devanagari=True, chandas=False) - .build() - .to_item() - .show() - ) - listen_iast = ( - RecitationTimeline(sloka=sloka, devanagari=False, chandas=False) - .build() - .to_item() - .show() - ) - recitation = ( - RecitationTimeline(sloka=sloka, devanagari=True, chandas=True) + quadrants = ( + QuadrantsTimeline( + [ + RecitationTimeline(sloka=sloka, devanagari=True, chandas=False), + RecitationTimeline( + sloka=sloka, devanagari=False, chandas=False + ), + RecitationTimeline(sloka=sloka, devanagari=True, chandas=True), + EnglishTimeline(sloka=sloka), + ] + ) .build() .to_item() .show() ) - translation = EnglishTimeline(sloka=sloka).build().to_item().show() - - TransformableFrameClip( - listen_deva, - offset=(-0.25, 0.25), - scale=0.5, - ).show() - TransformableFrameClip( - listen_iast, - offset=(+0.25, 0.25), - scale=0.5, - ).show() - TransformableFrameClip( - recitation, - offset=(-0.25, -0.25), - scale=0.5, - ).show() - TransformableFrameClip( - translation, - offset=(+0.25, -0.25), - scale=0.5, - ).show() - - self.forward( - max( - listen_deva.duration, - listen_iast.duration, - recitation.duration, - translation.duration, - ) - ) - # self.prepare(FadeOut(translation), duration=1.0) - - self.play(FadeOut(Group(*borders))) - """ for sloka in self.slokas: - # introduction = IntroduceSloka(sloka).build().to_item() - - # left = sloka_group(sloka) - # right = sloka_group(sloka) - # left.points.scale(0.5) - # right.points.scale(0.5) - # right.points.to_border(UR, buff=MED_SMALL_BUFF) - - scaledown = 0.5 - - def place_in_corner(clip, corner): - # TODO: fiddle w this - clip.transform.set(scale=scaledown) - clip.points.scale(scaledown) - clip.points.to_border(corner, buff=0) - - listen_deva = sloka_group_reformed(sloka, devanagari=True) - speak_deva = sloka_group_reformed(sloka, devanagari=True) - - listen_iast = sloka_group_reformed(sloka, devanagari=False) - # speak_iast = sloka_group_reformed(sloka, devanagari=False) - - listen_deva_clip = RectClip(listen_deva, anchor=ORIGIN, border=True) - speak_deva_clip = RectClip(speak_deva, anchor=ORIGIN, border=True) - listen_iast_clip = RectClip(listen_iast, anchor=ORIGIN, border=True) - # speak_iast_clip = RectClip(speak_iast, anchor=ORIGIN, border=True) - - place_in_corner(listen_deva_clip, UL) - place_in_corner(listen_iast_clip, UR) - place_in_corner(speak_deva_clip, DOWN) - # speak_deva.points.scale((2.0, 1.0, 1.0)) - speak_deva_clip.points.scale((2.0, 1.0, 1.0)) - - iiiii = SurroundingRect(speak_deva, buff=0) - # place_in_corner(speak_iast_clip, DR) - - listen_deva.points.shift(DOWN * 6) - listen_deva.points.scale(0.5) - listen_iast.points.shift(DOWN * 6) - listen_iast.points.scale(0.5) - - self.play( - Succession( - Aligned( - Aligned( - listen_deva.anim.points.scale(2.0), - listen_deva.anim.points.shift(UP * 6), - listen_iast.anim.points.scale(2.0), - listen_iast.anim.points.shift(UP * 6), - ), - FadeIn( - Group( - iiiii, - listen_deva, - listen_deva_clip, - listen_iast, - listen_iast_clip, - speak_deva, - speak_deva_clip, - # speak_iast, - # speak_iast_clip, - ) - ), - ), - Wait(1.0), - # Aligned( - # left.anim.points.shift(LEFT * scaledown * 6), - # right.anim.points.shift(RIGHT * scaledown * 6), - # right.anim.points.scale(0.2) - # ), - Wait(1.0), - # FadeOut(Group(lt, left, rt, right)) - ) - ) - - blank_deva = sloka_group_chandas( - sloka, blank=True, matras=False, devanagari=True - ) - chandas_deva = sloka_group_chandas( - sloka, blank=False, matras=False, devanagari=True - ) - # blank_iast = sloka_group_chandas( - # sloka, blank=True, matras=False, devanagari=False - # ) - # chandas_iast = sloka_group_chandas( - # sloka, blank=False, matras=False, devanagari=False - # ) - - speak_deva_clip.apply( - iiiii, - blank_deva.text, - blank_deva.keys, - chandas_deva.text, - chandas_deva.keys, - ) - # speak_iast_clip.apply( - # blank_iast.text, blank_iast.keys, chandas_iast.text, chandas_iast.keys - # ) - - self.play( - Aligned( - LenientTransformMatchingDiff(speak_deva, blank_deva.text), - # LenientTransformMatchingDiff(speak_iast, blank_iast.text), - ) - ) - self.play( - Aligned( - Transform(blank_deva.text, chandas_deva.text), - # Transform(blank_iast.text, chandas_iast.text), - ) - ) - self.play(Wait(1.0)) - self.play( - FadeIn( - Group( - chandas_deva.keys, - # chandas_iast.keys - ) - ) - ) - self.prepare( - Aligned( - Aligned( - listen_deva.anim.points.scale(0.5), - listen_deva.anim.points.shift(UP * 6), - listen_iast.anim.points.scale(0.5), - listen_iast.anim.points.shift(UP * 6), - ), - FadeOut( - Group( - listen_deva_clip, - listen_iast_clip, - chandas_deva.keys, - chandas_deva.text, - # chandas_iast.keys, - # chandas_iast.text, - speak_deva_clip, - iiiii, - # speak_iast_clip, - ) - ), - ), - duration=1.0, - ) """ - - # self.play(FadeOut(title)) - - # group.points.scale(factor) - # scale_with_stroke(left, 0.5) - # scale_with_stroke(right, 0.5) - # introduction.show() - # self.forward_to(introduction.end) - - # for sloka in self.slokas: - # # explain = build_explain_sloka_cached(sloka).to_item().show() - # explain = ExplainSloka(sloka).build().to_item().show() - # self.forward_to(explain.end) - - # thumbnail = sloka_thumbnail(sloka) - # initial = sloka_group(sloka) - # self.play(Write(initial), duration=0.33) - # self.play(LenientTransformMatchingDiff(initial, thumbnail[0]), duration=0.33) - # self.play(Aligned(FadeIn(thumbnail[1:]), Sleep(thumbnail[0]))) - # - # for li, line in enumerate(sloka.lines): - # for vi, vAkya in enumerate(line.vAkyAni): - # if li != 0 or vi != 0: - # self.play(Sleep(thumbnail[0])) - # - # selection = thumbnail[0][li].get_label( - # f"line_{li}_utterance_{vi}" - # ) - # self.play(Awaken(selection)) - # - # vt = UtteranceTimeline(vAkya).build().to_item().show() - # self.forward_to(vt.end) - # - # self.play(FadeOut(thumbnail)) + self.forward(quadrants.duration) From 2bac1c777e34ad31337b7563292ca29bfcc45c6d Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Thu, 28 May 2026 12:05:13 -0400 Subject: [PATCH 31/50] explain sloka --- nirukta/timelines/explain_sloka.py | 47 +++++++++--------------------- nirukta/timelines/sloka_file.py | 23 +++++---------- nirukta/timelines/sutra_file.py | 11 ++++--- nirukta/timelines/thumbnail.py | 1 + 4 files changed, 28 insertions(+), 54 deletions(-) diff --git a/nirukta/timelines/explain_sloka.py b/nirukta/timelines/explain_sloka.py index 18ea724..b5ef186 100644 --- a/nirukta/timelines/explain_sloka.py +++ b/nirukta/timelines/explain_sloka.py @@ -1,26 +1,18 @@ import hashlib import dill as pickle -from typing import Any, List +from typing import Any from janim.imports import ( YELLOW, - Aligned, - FadeIn, - FadeOut, - Succession, Timeline, - Write, + Wait, TransformableFrameClip, ) from janim.logger import log -from nirukta.models import Line, Sloka -from nirukta.render import Awaken, Sleep +from nirukta.models import Sloka from nirukta.timelines import ( - LenientTransformMatchingDiff, - UtteranceTimeline, build_utterance_cached, ) -from nirukta.timelines.line import LineTimeline from nirukta.timelines.thumbnail import ThumbnailTimeline # Memory-only cache: disk caching is handled at the utterance level. @@ -51,32 +43,21 @@ def gui_color(self) -> str: def construct(self): thumb = ThumbnailTimeline(sloka=self.sloka).build().to_item().show() - TransformableFrameClip( thumb, offset=(-0.25, 0.25), scale=0.5, ).show() - self.forward_to(thumb.end) + self.play(Wait(1.0)) + + for li, line in enumerate(self.sloka.lines): + for vi, vAkya in enumerate(line.vAkyAni): + if li != 0 or vi != 0: + self.play(Wait(0.33)) - # thumbnail = sloka_thumbnail(self.sloka) - # # initial = sloka_group(self.sloka) - # # self.play(Write(initial), duration=0.33) - # # self.play(LenientTransformMatchingDiff(initial, thumbnail[0]), duration=0.33) - # # self.play(Aligned(FadeIn(thumbnail[1:]), Sleep(thumbnail[0]))) - # self.play(Aligned(FadeIn(thumbnail), Sleep(thumbnail[0]))) - # - # for li, line in enumerate(self.sloka.lines): - # for vi, vAkya in enumerate(line.vAkyAni): - # if li != 0 or vi != 0: - # self.play(Sleep(thumbnail[0])) - # - # selection = thumbnail[0][li].get_label(f"line_{li}_utterance_{vi}") - # self.play(Awaken(selection)) - # - # vt = build_utterance_cached(vAkya).to_item().show() - # self.forward_to(vt.end) - # - # self.play(Sleep(thumbnail[0])) - # self.play(FadeOut(thumbnail)) + self.play(Wait(0.33)) + vt = build_utterance_cached(vAkya).to_item().show() + self.forward(vt.duration) + + self.forward_to(thumb.end) diff --git a/nirukta/timelines/sloka_file.py b/nirukta/timelines/sloka_file.py index 0f62436..3f3b4e1 100644 --- a/nirukta/timelines/sloka_file.py +++ b/nirukta/timelines/sloka_file.py @@ -1,7 +1,6 @@ -from janim.imports import ORANGE, FadeOut, Timeline, Wait, Write +from janim.imports import ORANGE, Timeline from nirukta.models import Sloka, SlokaFile -from nirukta.sloka import sloka_group_english -from nirukta.timelines.explain_sloka import ExplainSloka, build_explain_sloka_cached +from nirukta.timelines.explain_sloka import ExplainSloka from nirukta.timelines.introduce_sloka import IntroduceSloka @@ -23,16 +22,10 @@ def gui_color(self) -> str: return ORANGE def construct(self): - introduction = IntroduceSloka(self.sloka, self.citation).build().to_item() - introduction.show() - self.forward_to(introduction.end) + introduction = ( + IntroduceSloka(self.sloka, self.citation).build().to_item().show() + ) + self.forward(introduction.duration) - # explanation = build_explain_sloka_cached(self.sloka).to_item() - explanation = ExplainSloka(self.sloka).build().to_item() - explanation.show() - self.forward_to(explanation.end) - - # sge = sloka_group_english(self.sloka) - # self.play(Write(sge)) - # self.play(Wait(2.0)) - # self.play(FadeOut(sge)) + explanation = ExplainSloka(self.sloka).build().to_item().show() + self.forward(explanation.duration) diff --git a/nirukta/timelines/sutra_file.py b/nirukta/timelines/sutra_file.py index f62d380..78b1580 100644 --- a/nirukta/timelines/sutra_file.py +++ b/nirukta/timelines/sutra_file.py @@ -2,16 +2,10 @@ from typing import List from janim.imports import ( - Config, ORANGE, ORIGIN, - WHITE, - FadeIn, FadeOut, - Group, - Rect, Timeline, - TransformableFrameClip, TypstText, Wait, Write, @@ -22,6 +16,7 @@ set_font, typst_code, ) +from nirukta.timelines import ExplainSloka from nirukta.timelines.english import EnglishTimeline from nirukta.timelines.quadrants import QuadrantsTimeline from nirukta.timelines.recitation import RecitationTimeline @@ -77,3 +72,7 @@ def construct(self): .show() ) self.forward(quadrants.duration) + + for sloka in self.slokas: + explain = ExplainSloka(sloka=sloka).build().to_item().show() + self.forward(explain.duration) diff --git a/nirukta/timelines/thumbnail.py b/nirukta/timelines/thumbnail.py index 4bb35f0..5d6bee8 100644 --- a/nirukta/timelines/thumbnail.py +++ b/nirukta/timelines/thumbnail.py @@ -73,6 +73,7 @@ def construct(self): # self.play(Write(initial), duration=0.33) # self.play(LenientTransformMatchingDiff(initial, thumbnail[0]), duration=0.33) # self.play(Aligned(FadeIn(thumbnail[1:]), Sleep(thumbnail[0]))) + self.play(Aligned(FadeIn(group), Sleep(sloka_text))) print() From a9a2065a095590bfe635b95d1df233839622a916 Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Thu, 28 May 2026 12:05:39 -0400 Subject: [PATCH 32/50] fix subtle bug in english gloss searching --- nirukta/models/gloss.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/nirukta/models/gloss.py b/nirukta/models/gloss.py index 6cb69d3..99e420d 100644 --- a/nirukta/models/gloss.py +++ b/nirukta/models/gloss.py @@ -1,9 +1,12 @@ +import re from dataclasses import dataclass from typing import Set, Union from nirukta.inflection import Case, SanskritInflection from nirukta.strings import find_nth +_TYPST_CMD_RE = re.compile(r"#\w+\(\)") + @dataclass class EnglishGloss: @@ -18,6 +21,8 @@ class EnglishGloss: def find_reference( self, english: str, visited: Set[tuple[int, int]] ) -> tuple[int, int]: + typst_ranges = [(m.start(), m.end()) for m in _TYPST_CMD_RE.finditer(english)] + n = 1 while True: gi = find_nth(english, self.text, n) @@ -33,9 +38,13 @@ def find_reference( "Invalid gloss index into english text" ) + # Skip occurrences that land inside a Typst command like #linebreak() + if any(start <= gi < end for start, end in typst_ranges): + n += 1 + continue + # If we've already found this instance of the gloss text if index in visited: - # Find the next one n += 1 else: return index From f72978aaf6b4e0b46094b4525e7fd3b4fd5b6984 Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Thu, 28 May 2026 12:09:59 -0400 Subject: [PATCH 33/50] birthday.sloka --- library/birthday.sloka | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 library/birthday.sloka diff --git a/library/birthday.sloka b/library/birthday.sloka new file mode 100644 index 0000000..d596c73 --- /dev/null +++ b/library/birthday.sloka @@ -0,0 +1,15 @@ +=== sloka === + +--- line --- +amBojam[the day lotus]+iva[like]=amBojamiva +(puzpARAm[among flowers]+amftam[nectar]=puzpARAmamftam)=puzpARAmamftaM +Bojanezu[among foods]+iva[like]=Bojanezviva . +"like the day lotus among flowers," +"like nectar among foods," + +--- line --- +sUryaH[the sun]=sUryo +jyotir[heavenly bodies]+gaRasya[among all]+iva[like]=jyotirgaRasyeva +taTA[likewise]+eva[indeed]=taTEva te[your] saKitvanam[friendship] .. +"like the sun among all the heavenly bodies," +"likewise indeed is your friendship." From 7ec4ecb58d6cdd6e65a05685b69e00d69b07c981 Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Thu, 28 May 2026 12:19:34 -0400 Subject: [PATCH 34/50] make quad reusable --- library/sUtrARi/vakratuRqa_mahAkAya.sutra | 2 +- nirukta/timelines/introduce_quad.py | 43 ++++++++++++++++++++++ nirukta/timelines/introduce_sloka.py | 5 --- nirukta/timelines/sloka_file.py | 4 +++ nirukta/timelines/sutra_file.py | 44 +++++++++-------------- 5 files changed, 65 insertions(+), 33 deletions(-) create mode 100644 nirukta/timelines/introduce_quad.py diff --git a/library/sUtrARi/vakratuRqa_mahAkAya.sutra b/library/sUtrARi/vakratuRqa_mahAkAya.sutra index 738f274..ef95d8e 100644 --- a/library/sUtrARi/vakratuRqa_mahAkAya.sutra +++ b/library/sUtrARi/vakratuRqa_mahAkAya.sutra @@ -1,4 +1,4 @@ -=== meow === +=== unknown === === sloka === diff --git a/nirukta/timelines/introduce_quad.py b/nirukta/timelines/introduce_quad.py new file mode 100644 index 0000000..0f20ce2 --- /dev/null +++ b/nirukta/timelines/introduce_quad.py @@ -0,0 +1,43 @@ +from dataclasses import dataclass + +from janim.imports import ( + ORANGE, + Timeline, +) +from nirukta.models import Sloka +from nirukta.timelines.english import EnglishTimeline +from nirukta.timelines.quadrants import QuadrantsTimeline +from nirukta.timelines.recitation import RecitationTimeline + + +@dataclass +class IntroduceQuadTimeline(Timeline): + slokas: Sloka + + def __init__(self, sloka: Sloka): + super().__init__() + self.sloka = sloka + + @property + def gui_name(self) -> str: + return "IntroduceQuad" + + @property + def gui_color(self) -> str: + return ORANGE + + def construct(self): + quadrants = ( + QuadrantsTimeline( + [ + RecitationTimeline(self.sloka, devanagari=True, chandas=False), + RecitationTimeline(self.sloka, devanagari=False, chandas=False), + RecitationTimeline(self.sloka, devanagari=True, chandas=True), + EnglishTimeline(self.sloka), + ] + ) + .build() + .to_item() + .show() + ) + self.forward(quadrants.duration) diff --git a/nirukta/timelines/introduce_sloka.py b/nirukta/timelines/introduce_sloka.py index 1f328c2..60f4d80 100644 --- a/nirukta/timelines/introduce_sloka.py +++ b/nirukta/timelines/introduce_sloka.py @@ -4,7 +4,6 @@ from janim.imports import ( FadeIn, DOWN, - UP, YELLOW, FadeOut, Group, @@ -13,21 +12,17 @@ Wait, Write, Aligned, - LEFT, ) from nirukta.constants import SANSKRIT_FONT, SCALE from nirukta.models import Language, Sloka from nirukta.render import ( - FlatAligned, set_font, - transform_text, typst_code, ) from nirukta.sloka import ( sloka_group_chandas, sloka_group_reformed, - title_and_pada_labels, ) from nirukta.timelines.transform import LenientTransformMatchingDiff diff --git a/nirukta/timelines/sloka_file.py b/nirukta/timelines/sloka_file.py index 3f3b4e1..f12abcf 100644 --- a/nirukta/timelines/sloka_file.py +++ b/nirukta/timelines/sloka_file.py @@ -2,6 +2,7 @@ from nirukta.models import Sloka, SlokaFile from nirukta.timelines.explain_sloka import ExplainSloka from nirukta.timelines.introduce_sloka import IntroduceSloka +from nirukta.timelines.introduce_quad import IntroduceQuadTimeline class SlokaFileTimeline(Timeline): @@ -27,5 +28,8 @@ def construct(self): ) self.forward(introduction.duration) + quadrants = IntroduceQuadTimeline(self.sloka).build().to_item().show() + self.forward(quadrants.duration) + explanation = ExplainSloka(self.sloka).build().to_item().show() self.forward(explanation.duration) diff --git a/nirukta/timelines/sutra_file.py b/nirukta/timelines/sutra_file.py index 78b1580..5d5ed42 100644 --- a/nirukta/timelines/sutra_file.py +++ b/nirukta/timelines/sutra_file.py @@ -5,6 +5,7 @@ ORANGE, ORIGIN, FadeOut, + Succession, Timeline, TypstText, Wait, @@ -18,6 +19,7 @@ ) from nirukta.timelines import ExplainSloka from nirukta.timelines.english import EnglishTimeline +from nirukta.timelines.introduce_quad import IntroduceQuadTimeline from nirukta.timelines.quadrants import QuadrantsTimeline from nirukta.timelines.recitation import RecitationTimeline @@ -41,36 +43,24 @@ def gui_color(self) -> str: return ORANGE def construct(self): - citation = TypstText( - set_font(typst_code(self.citation, Language.SANSKRIT), SANSKRIT_FONT), - scale=SCALE, - ) - citation.points.move_to(ORIGIN) - - # Introduce the text by its title - for animation in [ - Write(citation), - Wait(1.5), - FadeOut(citation), - ]: - self.play(animation) + if self.citation != "unknown": + citation = TypstText( + set_font(typst_code(self.citation, Language.SANSKRIT), SANSKRIT_FONT), + scale=SCALE, + ) + citation.points.move_to(ORIGIN) - for sloka in self.slokas: - quadrants = ( - QuadrantsTimeline( - [ - RecitationTimeline(sloka=sloka, devanagari=True, chandas=False), - RecitationTimeline( - sloka=sloka, devanagari=False, chandas=False - ), - RecitationTimeline(sloka=sloka, devanagari=True, chandas=True), - EnglishTimeline(sloka=sloka), - ] + # Introduce the text by its title + self.play( + Succession( + Write(citation), + Wait(1.5), + FadeOut(citation), ) - .build() - .to_item() - .show() ) + + for sloka in self.slokas: + quadrants = IntroduceQuadTimeline(sloka).build().to_item().show() self.forward(quadrants.duration) for sloka in self.slokas: From f76a02b22080ddc959e874024f8e0376c7129218 Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Thu, 28 May 2026 12:48:36 -0400 Subject: [PATCH 35/50] add scale arg to quads --- nirukta/timelines/introduce_quad.py | 3 ++- nirukta/timelines/quadrants.py | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/nirukta/timelines/introduce_quad.py b/nirukta/timelines/introduce_quad.py index 0f20ce2..52f844a 100644 --- a/nirukta/timelines/introduce_quad.py +++ b/nirukta/timelines/introduce_quad.py @@ -34,7 +34,8 @@ def construct(self): RecitationTimeline(self.sloka, devanagari=False, chandas=False), RecitationTimeline(self.sloka, devanagari=True, chandas=True), EnglishTimeline(self.sloka), - ] + ], + scale=0.7, ) .build() .to_item() diff --git a/nirukta/timelines/quadrants.py b/nirukta/timelines/quadrants.py index b6a226f..efedce6 100644 --- a/nirukta/timelines/quadrants.py +++ b/nirukta/timelines/quadrants.py @@ -17,14 +17,16 @@ @dataclass class QuadrantsTimeline(Timeline): timelines: List[Timeline] + scale: float - def __init__(self, timelines: List[Timeline]): + def __init__(self, timelines: List[Timeline], scale: float = 0.5): # Exit early if args are invalid if len(timelines) > 4 or len(timelines) < 1: raise ValueError(f"Cannot display ${len(timelines)} in a quadrant layout.") super().__init__() self.timelines = timelines + self.scale = scale @property def gui_name(self) -> str: @@ -62,7 +64,7 @@ def construct(self): for idx, timeline in enumerate(self.timelines): built_timeline = timeline.build().to_item().show() TransformableFrameClip( - built_timeline, offset=offsets[idx], scale=0.5 + built_timeline, offset=offsets[idx], scale=self.scale ).show() durations.append(built_timeline.duration) From 49da8ac800ff447f0357f44a3ecab911defd2747 Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Thu, 28 May 2026 12:49:09 -0400 Subject: [PATCH 36/50] replace vertical grid with linebreaks --- nirukta/sloka.py | 13 ++++++++----- nirukta/timelines/thumbnail.py | 1 + nirukta/typst.py | 10 +++++----- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/nirukta/sloka.py b/nirukta/sloka.py index 33c3e3f..0e8209e 100644 --- a/nirukta/sloka.py +++ b/nirukta/sloka.py @@ -21,7 +21,7 @@ from nirukta.render import set_font, transform_text, typst_code from typing import List -from nirukta.typst import arrange_vertical, box_cell, arrange_horizontal +from nirukta.typst import add_linebreaks, arrange_vertical, box_cell, arrange_horizontal """ def sloka_group(sloka: Sloka) -> Group[TypstText]: @@ -68,7 +68,8 @@ def sloka_group_english(sloka: Sloka) -> TypstText: rows.append(typst_code(english, Language.ENGLISH)) - grid = arrange_vertical(rows, gutter=0.6) + # grid = arrange_vertical(rows, gutter=0.6) + grid = add_linebreaks(rows) return TypstText( set_font(grid, LATIN_FONT), @@ -102,9 +103,11 @@ def sloka_group_reformed(sloka: Sloka, devanagari: bool) -> TypstText: ) sanskritcode += utterance_code + " " - rows.append(f"[{sanskritcode}]") + # rows.append(f"[{sanskritcode}]") + rows.append(sanskritcode) - grid = arrange_vertical(rows, gutter=0.6) + # grid = arrange_vertical(rows, gutter=0.6) + grid = add_linebreaks(rows) return TypstText( set_font(grid, font), @@ -131,7 +134,6 @@ def sloka_group_chandas( lang = Language.TRANSLIT font = LATIN_FONT - all_cells = [] cell_idx = 0 @@ -164,6 +166,7 @@ def sloka_group_chandas( row_labels.append(row_label) grid_code = arrange_vertical(rows) + # grid_code = add_linebreaks(rows) grid = TypstText(set_font(grid_code, font), scale=SCALE) if blank: diff --git a/nirukta/timelines/thumbnail.py b/nirukta/timelines/thumbnail.py index 5d6bee8..9f9ed40 100644 --- a/nirukta/timelines/thumbnail.py +++ b/nirukta/timelines/thumbnail.py @@ -88,6 +88,7 @@ def construct(self): selection = sloka_text.get_label(f"line_{li}_utterance_{vi}") self.play(Awaken(selection)) + # Build but do not show; so that we can match durations vt = build_utterance_cached(vAkya).to_item() self.forward(vt.duration) diff --git a/nirukta/typst.py b/nirukta/typst.py index 5591161..6397807 100644 --- a/nirukta/typst.py +++ b/nirukta/typst.py @@ -15,11 +15,7 @@ def box_cell( if fill is None: fill = "rgb(0, 0, 0, 0)" - width=( - height * 2 + gutter - if wide - else height - ) + width = height * 2 + gutter if wide else height return ( f"[#box(fill: {fill}, width: {width}em, height: {height}em, radius: 0.4em)" @@ -44,3 +40,7 @@ def arrange_vertical(cells: List[str], gutter: float = gutter) -> str: return ( f"#grid(rows: (auto,) * {len(cells)}, gutter: {gutter}em, {', '.join(cells)})" ) + + +def add_linebreaks(lines: List[str]) -> str: + return f"{' #linebreak() '.join(lines)}" From 12707b6218c69c73b3524d79007fc7429fb394b9 Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Thu, 28 May 2026 13:13:55 -0400 Subject: [PATCH 37/50] turn off caching for now --- nirukta/timelines/explain_sloka.py | 7 +++---- nirukta/timelines/thumbnail.py | 3 ++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/nirukta/timelines/explain_sloka.py b/nirukta/timelines/explain_sloka.py index b5ef186..f0d2c0b 100644 --- a/nirukta/timelines/explain_sloka.py +++ b/nirukta/timelines/explain_sloka.py @@ -10,9 +10,7 @@ ) from janim.logger import log from nirukta.models import Sloka -from nirukta.timelines import ( - build_utterance_cached, -) +from nirukta.timelines import build_utterance_cached, UtteranceTimeline from nirukta.timelines.thumbnail import ThumbnailTimeline # Memory-only cache: disk caching is handled at the utterance level. @@ -57,7 +55,8 @@ def construct(self): self.play(Wait(0.33)) self.play(Wait(0.33)) - vt = build_utterance_cached(vAkya).to_item().show() + # vt = build_utterance_cached(vAkya).to_item().show() + vt = UtteranceTimeline(vAkya).build().to_item().show() self.forward(vt.duration) self.forward_to(thumb.end) diff --git a/nirukta/timelines/thumbnail.py b/nirukta/timelines/thumbnail.py index 9f9ed40..97eefcb 100644 --- a/nirukta/timelines/thumbnail.py +++ b/nirukta/timelines/thumbnail.py @@ -89,7 +89,8 @@ def construct(self): self.play(Awaken(selection)) # Build but do not show; so that we can match durations - vt = build_utterance_cached(vAkya).to_item() + # vt = build_utterance_cached(vAkya).to_item() + vt = UtteranceTimeline(vAkya).build().to_item() self.forward(vt.duration) self.play(Sleep(sloka_text)) From 2cabeb883c6ce3387d429783e8616cc1b11471d3 Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Thu, 28 May 2026 13:16:05 -0400 Subject: [PATCH 38/50] add `first` and `last` fields to quads --- nirukta/timelines/introduce_quad.py | 10 ++++++++-- nirukta/timelines/quadrants.py | 18 +++++++++++++++--- nirukta/timelines/sloka_file.py | 7 ++++++- nirukta/timelines/sutra_file.py | 11 +++++++++-- 4 files changed, 38 insertions(+), 8 deletions(-) diff --git a/nirukta/timelines/introduce_quad.py b/nirukta/timelines/introduce_quad.py index 52f844a..a56ed80 100644 --- a/nirukta/timelines/introduce_quad.py +++ b/nirukta/timelines/introduce_quad.py @@ -13,10 +13,14 @@ @dataclass class IntroduceQuadTimeline(Timeline): slokas: Sloka + first: bool + last: bool - def __init__(self, sloka: Sloka): + def __init__(self, sloka: Sloka, first: bool, last: bool): super().__init__() self.sloka = sloka + self.first = first + self.last = last @property def gui_name(self) -> str: @@ -35,7 +39,9 @@ def construct(self): RecitationTimeline(self.sloka, devanagari=True, chandas=True), EnglishTimeline(self.sloka), ], - scale=0.7, + first=self.first, + last=self.last, + scale=0.5, ) .build() .to_item() diff --git a/nirukta/timelines/quadrants.py b/nirukta/timelines/quadrants.py index efedce6..ef3e42e 100644 --- a/nirukta/timelines/quadrants.py +++ b/nirukta/timelines/quadrants.py @@ -18,8 +18,12 @@ class QuadrantsTimeline(Timeline): timelines: List[Timeline] scale: float + first: bool + last: bool - def __init__(self, timelines: List[Timeline], scale: float = 0.5): + def __init__( + self, timelines: List[Timeline], first: bool, last: bool, scale: float = 0.5 + ): # Exit early if args are invalid if len(timelines) > 4 or len(timelines) < 1: raise ValueError(f"Cannot display ${len(timelines)} in a quadrant layout.") @@ -27,6 +31,8 @@ def __init__(self, timelines: List[Timeline], scale: float = 0.5): super().__init__() self.timelines = timelines self.scale = scale + self.first = first + self.last = last @property def gui_name(self) -> str: @@ -55,7 +61,12 @@ def construct(self): border = Rect(quad_w, quad_h, color=WHITE, stroke_radius=0.03) border.points.move_to(center) borders.append(border) - self.play(FadeIn(Group(*borders))) + + borders = Group(*borders) + if self.first: + self.play(FadeIn(borders)) + else: + borders.show() # for sloka in self.timelines: offsets = [(-0.25, +0.25), (+0.25, +0.25), (-0.25, -0.25), (+0.25, -0.25)] @@ -71,4 +82,5 @@ def construct(self): # Wait self.forward(max(durations)) - self.play(FadeOut(Group(*borders))) + if self.last: + self.play(FadeOut(borders)) diff --git a/nirukta/timelines/sloka_file.py b/nirukta/timelines/sloka_file.py index f12abcf..553b904 100644 --- a/nirukta/timelines/sloka_file.py +++ b/nirukta/timelines/sloka_file.py @@ -28,7 +28,12 @@ def construct(self): ) self.forward(introduction.duration) - quadrants = IntroduceQuadTimeline(self.sloka).build().to_item().show() + quadrants = ( + IntroduceQuadTimeline(self.sloka, first=True, last=True) + .build() + .to_item() + .show() + ) self.forward(quadrants.duration) explanation = ExplainSloka(self.sloka).build().to_item().show() diff --git a/nirukta/timelines/sutra_file.py b/nirukta/timelines/sutra_file.py index 5d5ed42..6d28101 100644 --- a/nirukta/timelines/sutra_file.py +++ b/nirukta/timelines/sutra_file.py @@ -59,8 +59,15 @@ def construct(self): ) ) - for sloka in self.slokas: - quadrants = IntroduceQuadTimeline(sloka).build().to_item().show() + for idx, sloka in enumerate(self.slokas): + quadrants = ( + IntroduceQuadTimeline( + sloka, first=(idx == 0), last=(idx == len(self.slokas) - 1) + ) + .build() + .to_item() + .show() + ) self.forward(quadrants.duration) for sloka in self.slokas: From 5d6d0071bec0bd7e4e69f4b2f6731b9c4be3a06e Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Thu, 28 May 2026 17:17:26 -0400 Subject: [PATCH 39/50] auto-resizing chandas font for frame width --- library/test.sloka | 42 +++++++++++++++--- nirukta/render.py | 8 ++-- nirukta/sloka.py | 46 +++++++------------ nirukta/timelines/explain_sloka.py | 9 ++++ nirukta/timelines/introduce_sloka.py | 66 ++++++++++++++-------------- nirukta/timelines/recitation.py | 11 +++-- nirukta/timelines/utterance.py | 4 ++ nirukta/typst.py | 3 +- 8 files changed, 112 insertions(+), 77 deletions(-) diff --git a/library/test.sloka b/library/test.sloka index b11f15d..054e641 100644 --- a/library/test.sloka +++ b/library/test.sloka @@ -1,8 +1,38 @@ -=== SrIsarasvatIstotraM 7 === +=== sloka === --- line --- -nitya[constant]+Ananda[in][bliss]+e[O]{VOC}=nityAnande{COMP.BV.F.SG.VOC} -nir[un]+ADara[supported]+e[O]{VOC}=nirADAre{COMP.TP.F.SG.VOC} -nizkala[undivided]+yE[to her]{DAT}=nizkalAyE{ADJ.F.SG.DAT} namaH[reverence]=namo namaH[reverence] . -"O you in constant bliss, O unsupported one -" -"reverence, reverence to her who is undivided." +yA[She who] +kunda[Jasmine flower]+indu[Moon]+tuzAra[frosty]+hAra[garland of pearls]+Davala[dazzling]=kundendutuzArahAraDavalA +"She who is white like Jasmine flower," +"frosty like the Moon," +"and dazzling like a garland of pearls." + +yA[She who] +SuBra[shining]+vastra[clothing]+Avfta[covered]=SuBravastrAvftA +"She who is covered by shining clothing." + +--- line --- +yA[She whose] +vIRA[veena]+(vara[boon]+daRqa[staff]=varadaRqa)+maRqita[adorned]+karA[hands]=vIRAvaradaRqamaRqitakarA +"She whose hands are adorned by veena" +"and boon-giving staff;" + +yA[She who] +Sveta[white]+padma[lotus]+AsanA[seated]=SvetapadmAsanA . +"She who is seated on a white lotus." + +--- line --- +yA[She who] +(brahma[Brahma]+acyuta[Viṣṇu]+SaNkara[Śiva]+praBftiBiH[and other]+deva[devas]=brahmAcyutaSaNkarapraBftiBirdeva)+sadA[always]=brahmAcyutaSaNkarapraBftiBirdevEssadA +pUjitA[worshipped] +"She who is always worshipped by Brahma, Viṣṇu, Śiva, and other devas." + +--- line --- +sA[that] +mAM[me] +pAtu[May][protect] +sarasvatI[Sarasvatī] +BagavatI[divine] +niSSeza[completely]+jAqya[ignorance]+apahA[removes]=niSSezajAqyApahA .. 1 .. +"May that divine Sarasvatī who removes ignorance completely, protect me." + diff --git a/nirukta/render.py b/nirukta/render.py index 4e9a179..1a10e7e 100644 --- a/nirukta/render.py +++ b/nirukta/render.py @@ -138,7 +138,7 @@ def rate_func(self): if self.anim == Animation.EXPAND: return rush_into else: - return linear + return double_smooth def duration(self): match self.anim: @@ -195,10 +195,10 @@ def T(s): return f"#box[{inner}]" -def set_font(text: str, font: str): +def set_font(text: str, font: str, size: str = "11pt"): return ( - f'#set text(font: "{font}", size: 6pt, stroke: none)\n' - # f"#set page(width: {266 * SCALE}pt)\n" + f'#set text(font: "{font}", size: {size}, stroke: none)\n' + f"#set page(width: {266 * SCALE}pt)\n" f"{text}" ) diff --git a/nirukta/sloka.py b/nirukta/sloka.py index 0e8209e..64bd0e7 100644 --- a/nirukta/sloka.py +++ b/nirukta/sloka.py @@ -13,6 +13,7 @@ SurroundingRect, Text, TypstText, + Config, ) from nirukta.constants import SANSKRIT_FONT, LATIN_FONT, SCALE from nirukta.models import Language, Sloka @@ -166,23 +167,30 @@ def sloka_group_chandas( row_labels.append(row_label) grid_code = arrange_vertical(rows) - # grid_code = add_linebreaks(rows) - grid = TypstText(set_font(grid_code, font), scale=SCALE) + + # Actual ratio of text to use + columns = len(padas[0]) + ratio = Config.get.frame_width / columns + + grid = TypstText(set_font(grid_code, font, f"{ratio}em"), scale=SCALE) if blank: return Keyed(text=grid, keys=Group()) - t = title_and_pada_labels(meter_label, grid, row_labels) + t = title_and_pada_labels(meter_label, grid, row_labels, ratio) + return Keyed(text=grid, keys=t) def title_and_pada_labels( - meter_label: str, texttttt: TypstText, labels: List[str] + meter_label: str, texttttt: TypstText, labels: List[str], ratio: float ) -> Group: # Position title and labels relative to the centered grid meter_deva = transform_text(meter_label, Language.SANSKRIT) title = TypstText( - set_font(f"#text(fill: white, size: 1.2em)[{meter_deva}]", SANSKRIT_FONT), + set_font( + f"#text(fill: white, size: {ratio * 1.2}em)[{meter_deva}]", SANSKRIT_FONT + ), scale=SCALE, ) title.points.next_to(texttttt, UP) @@ -191,33 +199,13 @@ def title_and_pada_labels( for pada_idx, c_label in enumerate(labels): label_text = pada_labels[pada_idx] if pada_idx < len(pada_labels) else "" label = TypstText( - set_font(f"#text(fill: white, size: 0.85em)[{label_text}]", SANSKRIT_FONT), + set_font( + f"#text(fill: white, size: {ratio}em)[{label_text}]", + SANSKRIT_FONT, + ), scale=SCALE, ) label.points.next_to(texttttt.get_label(c_label), LEFT) labelz.append(label) return Group(title, *labelz) - - -""" def sloka_thumbnail(sloka: Sloka) -> Group: - sloka_text = sloka_group_reformed(sloka, devanagari=True) - if sloka.number is not None: - number_label = Group( - Rect(0.4, 0.4, fill_alpha=0.3), - Text(f"{sloka.number}", font_size=22), - ) - number_label.points.next_to( - sloka_text, UP, buff=MED_SMALL_BUFF, aligned_edge=LEFT - ) - sloka_border = SurroundingRect( - Group(sloka_text, number_label), color=WHITE, buff=MED_SMALL_BUFF - ) - - group = Group(sloka_text, sloka_border, number_label) - else: - sloka_border = SurroundingRect(sloka_text, color=WHITE, buff=MED_SMALL_BUFF) - group = Group(sloka_text, sloka_border) - - group.points.to_border(UL, buff=MED_SMALL_BUFF) - return group """ diff --git a/nirukta/timelines/explain_sloka.py b/nirukta/timelines/explain_sloka.py index f0d2c0b..2481e90 100644 --- a/nirukta/timelines/explain_sloka.py +++ b/nirukta/timelines/explain_sloka.py @@ -40,6 +40,15 @@ def gui_color(self) -> str: return YELLOW def construct(self): + # TODO: find a way to run the thumbnail timeline without building the utterances twice + # utterance_timelines = [] + # timeline_durations = [] + # for li, line in enumerate(self.sloka.lines): + # for vi, vAkya in enumerate(line.vAkyAni): + # utterance_timelines.append(UtteranceTimeline(vAkya).build()) + # for timeline in utterance_timelines: + # timeline_durations.append(timeline.duration) + thumb = ThumbnailTimeline(sloka=self.sloka).build().to_item().show() TransformableFrameClip( thumb, diff --git a/nirukta/timelines/introduce_sloka.py b/nirukta/timelines/introduce_sloka.py index 60f4d80..8b8803b 100644 --- a/nirukta/timelines/introduce_sloka.py +++ b/nirukta/timelines/introduce_sloka.py @@ -49,35 +49,35 @@ def construct(self): self.play(Wait(1.0)) # Move glyphs into grid boxes - sloka_chandas_blank = sloka_group_chandas(self.sloka, blank=True) - sloka_chandas = sloka_group_chandas(self.sloka) - - self.play( - LenientTransformMatchingDiff( - sloka_g, sloka_chandas_blank.text, duration=0.5 - ) - ) - - self.play(Wait(1.0)) - - # Reveal the prosodic colors - self.play(Transform(sloka_chandas_blank.text, sloka_chandas.text, duration=0.6)) - self.play(Wait(1.0)) - # Reveal keys - self.play(FadeIn(sloka_chandas.keys)) - - self.play(Wait(2.0)) - - # Expand boxes by vowel duration - sloka_matras = sloka_group_chandas(self.sloka, matras=True) - - self.play( - Aligned( - Transform(sloka_chandas.text, sloka_matras.text), - Transform(sloka_chandas.keys, sloka_matras.keys), - duration=0.5, - ) - ) + # sloka_chandas_blank = sloka_group_chandas(self.sloka, blank=True) + # sloka_chandas = sloka_group_chandas(self.sloka) + # + # self.play( + # LenientTransformMatchingDiff( + # sloka_g, sloka_chandas_blank.text, duration=0.5 + # ) + # ) + # + # self.play(Wait(1.0)) + # + # # Reveal the prosodic colors + # self.play(Transform(sloka_chandas_blank.text, sloka_chandas.text, duration=0.6)) + # self.play(Wait(1.0)) + # # Reveal keys + # self.play(FadeIn(sloka_chandas.keys)) + # + # self.play(Wait(2.0)) + # + # # Expand boxes by vowel duration + # sloka_matras = sloka_group_chandas(self.sloka, matras=True) + # + # self.play( + # Aligned( + # Transform(sloka_chandas.text, sloka_matras.text), + # Transform(sloka_chandas.keys, sloka_matras.keys), + # duration=0.5, + # ) + # ) self.play(Wait(2.0)) @@ -87,14 +87,12 @@ def construct(self): scale=SCALE, ) print(citation_text.text) - citation_text.points.next_to(sloka_chandas.text, DOWN) + citation_text.points.next_to(sloka_g, DOWN) for animation in [ Write(citation_text, duration=1.0), Wait(1.0), - FadeOut( - Group(Group(sloka_matras.text, sloka_matras.keys), citation_text) - ), + FadeOut(Group(sloka_g, citation_text)), ]: self.play(animation) else: - self.play(FadeOut(Group(sloka_matras.text, sloka_matras.keys))) + self.play(FadeOut(sloka_g)) diff --git a/nirukta/timelines/recitation.py b/nirukta/timelines/recitation.py index a1af001..eaf49ae 100644 --- a/nirukta/timelines/recitation.py +++ b/nirukta/timelines/recitation.py @@ -30,7 +30,11 @@ def __init__(self, sloka: Sloka, devanagari: bool, chandas: bool): self.chandas = chandas def construct(self): - group = sloka_group_reformed(self.sloka, devanagari=self.devanagari) + if self.chandas: + c = sloka_group_chandas(self.sloka, blank=False, matras=False) + group = Group(c.text, c.keys) + else: + group = sloka_group_reformed(self.sloka, devanagari=self.devanagari) self.play( Succession( @@ -38,8 +42,9 @@ def construct(self): Wait(1.0), ) ) + self.play(Succession(Wait(2.0), FadeOut(group, duration=0.5))) - if self.chandas: + """ if self.chandas: blank = sloka_group_chandas(self.sloka, blank=True, matras=False) chandas = sloka_group_chandas(self.sloka, blank=False, matras=False) @@ -62,4 +67,4 @@ def construct(self): ) ) else: - self.play(Succession(Wait(2.0), FadeOut(group, duration=0.5))) + self.play(Succession(Wait(2.0), FadeOut(group, duration=0.5))) """ diff --git a/nirukta/timelines/utterance.py b/nirukta/timelines/utterance.py index 300aaad..68c982f 100644 --- a/nirukta/timelines/utterance.py +++ b/nirukta/timelines/utterance.py @@ -218,6 +218,10 @@ def construct(self): states[0][i].points.next_to(states[1][i], UP * SCALE) states[2][i].points.next_to(states[1][i], DOWN * SCALE) + # TODO: find a way to align on edges without making the english text move every expansion change + # states[0][i].points.next_to( states[1][i], direction=UP * SCALE, aligned_edge=LEFT) + # states[2][i].points.next_to( states[1][i], direction=DOWN * SCALE, aligned_edge=LEFT) + # Initial write on if i == 0: for fa in [ diff --git a/nirukta/typst.py b/nirukta/typst.py index 6397807..3391eef 100644 --- a/nirukta/typst.py +++ b/nirukta/typst.py @@ -1,7 +1,8 @@ from typing import List, Optional -height = 1.8 +# page_width = 266 +height = 1.5 gutter = 0.2 From 2d5cc162b9701a5f91a710f161148e4e13eb8fb4 Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Thu, 28 May 2026 17:25:50 -0400 Subject: [PATCH 40/50] prevent chandas from overscaling --- nirukta/sloka.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nirukta/sloka.py b/nirukta/sloka.py index 64bd0e7..3e41f63 100644 --- a/nirukta/sloka.py +++ b/nirukta/sloka.py @@ -170,7 +170,7 @@ def sloka_group_chandas( # Actual ratio of text to use columns = len(padas[0]) - ratio = Config.get.frame_width / columns + ratio = min(Config.get.frame_width / columns, 1.0) grid = TypstText(set_font(grid_code, font, f"{ratio}em"), scale=SCALE) From 931b05eb35e150ce92f7b8a6e3e9c098c17e6b60 Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Thu, 28 May 2026 17:38:43 -0400 Subject: [PATCH 41/50] removing `scale` --- nirukta/render.py | 8 ++--- nirukta/sloka.py | 52 ++-------------------------- nirukta/timelines/english.py | 2 -- nirukta/timelines/introduce_sloka.py | 1 - nirukta/timelines/sutra_file.py | 6 +--- nirukta/timelines/utterance.py | 11 +++--- nirukta/typst.py | 1 - 7 files changed, 11 insertions(+), 70 deletions(-) diff --git a/nirukta/render.py b/nirukta/render.py index 1a10e7e..928bec7 100644 --- a/nirukta/render.py +++ b/nirukta/render.py @@ -14,16 +14,14 @@ Group, GrowFromEdge, ShrinkToEdge, - VItem, ValueTracker, double_smooth, linear, rush_into, smooth, - there_and_back, ) -from nirukta.constants import INACTIVE, SCALE, TYPST_CMD_RE -from nirukta.models import Language, Sloka +from nirukta.constants import INACTIVE, TYPST_CMD_RE +from nirukta.models import Language from janim.imports import WHITE, C_LABEL_ANIM_ABSTRACT from aksharamukha import transliterate @@ -198,7 +196,7 @@ def T(s): def set_font(text: str, font: str, size: str = "11pt"): return ( f'#set text(font: "{font}", size: {size}, stroke: none)\n' - f"#set page(width: {266 * SCALE}pt)\n" + f"#set page(width: {266 * 1.3}pt)\n" f"{text}" ) diff --git a/nirukta/sloka.py b/nirukta/sloka.py index 3e41f63..bde0a4e 100644 --- a/nirukta/sloka.py +++ b/nirukta/sloka.py @@ -1,23 +1,13 @@ from attr import dataclass from janim.imports import ( - BLUE_E, - RED_E, LEFT, - MED_SMALL_BUFF, - UL, - AnimGroup, - Rect, - DOWN, UP, Group, - SurroundingRect, - Text, TypstText, Config, ) -from nirukta.constants import SANSKRIT_FONT, LATIN_FONT, SCALE +from nirukta.constants import SANSKRIT_FONT, LATIN_FONT from nirukta.models import Language, Sloka -from janim.imports import WHITE from nirukta.render import set_font, transform_text, typst_code from typing import List @@ -25,40 +15,6 @@ from nirukta.typst import add_linebreaks, arrange_vertical, box_cell, arrange_horizontal -""" def sloka_group(sloka: Sloka) -> Group[TypstText]: - group = [] - - for li, line in enumerate(sloka.lines): - sanskrit = "" - sanskritcode = "" - for vi, vAkya in enumerate(line.vAkyAni): - utterancetext = "" - for token in vAkya.tokens: - if isinstance(token, str): - sanskrit += token - utterancetext += token - else: - sanskrit += token.slp1 - utterancetext += token.slp1 - - sanskrit += " " - utterancetext += " " - utterance_code = f"{typst_code(utterancetext, Language.SANSKRIT)}" - sanskritcode += utterance_code + " " - - group.append( - TypstText( - set_font(sanskritcode, SANSKRIT_FONT), - scale=SCALE, - ) - ) - - group = Group(*group) - group.points.arrange(DOWN) - return group -""" - - def sloka_group_english(sloka: Sloka) -> TypstText: rows = [] @@ -74,7 +30,6 @@ def sloka_group_english(sloka: Sloka) -> TypstText: return TypstText( set_font(grid, LATIN_FONT), - scale=SCALE, ) @@ -112,7 +67,6 @@ def sloka_group_reformed(sloka: Sloka, devanagari: bool) -> TypstText: return TypstText( set_font(grid, font), - scale=SCALE, ) @@ -172,7 +126,7 @@ def sloka_group_chandas( columns = len(padas[0]) ratio = min(Config.get.frame_width / columns, 1.0) - grid = TypstText(set_font(grid_code, font, f"{ratio}em"), scale=SCALE) + grid = TypstText(set_font(grid_code, font, f"{ratio}em")) if blank: return Keyed(text=grid, keys=Group()) @@ -191,7 +145,6 @@ def title_and_pada_labels( set_font( f"#text(fill: white, size: {ratio * 1.2}em)[{meter_deva}]", SANSKRIT_FONT ), - scale=SCALE, ) title.points.next_to(texttttt, UP) pada_labels = [transform_text(str(n), Language.SANSKRIT) for n in range(1, 5)] @@ -203,7 +156,6 @@ def title_and_pada_labels( f"#text(fill: white, size: {ratio}em)[{label_text}]", SANSKRIT_FONT, ), - scale=SCALE, ) label.points.next_to(texttttt.get_label(c_label), LEFT) labelz.append(label) diff --git a/nirukta/timelines/english.py b/nirukta/timelines/english.py index 829c485..06efb55 100644 --- a/nirukta/timelines/english.py +++ b/nirukta/timelines/english.py @@ -12,7 +12,6 @@ from nirukta.models import Language, Sloka from nirukta.render import set_font, typst_code from nirukta.typst import arrange_vertical -from nirukta.util import SCALE @dataclass @@ -40,7 +39,6 @@ def construct(self): group = TypstText( set_font(grid, LATIN_FONT), - scale=SCALE, ) self.play( diff --git a/nirukta/timelines/introduce_sloka.py b/nirukta/timelines/introduce_sloka.py index 8b8803b..ce5e06e 100644 --- a/nirukta/timelines/introduce_sloka.py +++ b/nirukta/timelines/introduce_sloka.py @@ -84,7 +84,6 @@ def construct(self): if self.citation is not None and self.citation != "sloka": citation_text = TypstText( set_font(typst_code(self.citation, Language.SANSKRIT), SANSKRIT_FONT), - scale=SCALE, ) print(citation_text.text) citation_text.points.next_to(sloka_g, DOWN) diff --git a/nirukta/timelines/sutra_file.py b/nirukta/timelines/sutra_file.py index 6d28101..538012e 100644 --- a/nirukta/timelines/sutra_file.py +++ b/nirukta/timelines/sutra_file.py @@ -11,17 +11,14 @@ Wait, Write, ) -from nirukta.constants import SANSKRIT_FONT, SCALE +from nirukta.constants import SANSKRIT_FONT from nirukta.models import Language, Sloka, SutraFile from nirukta.render import ( set_font, typst_code, ) from nirukta.timelines import ExplainSloka -from nirukta.timelines.english import EnglishTimeline from nirukta.timelines.introduce_quad import IntroduceQuadTimeline -from nirukta.timelines.quadrants import QuadrantsTimeline -from nirukta.timelines.recitation import RecitationTimeline @dataclass @@ -46,7 +43,6 @@ def construct(self): if self.citation != "unknown": citation = TypstText( set_font(typst_code(self.citation, Language.SANSKRIT), SANSKRIT_FONT), - scale=SCALE, ) citation.points.move_to(ORIGIN) diff --git a/nirukta/timelines/utterance.py b/nirukta/timelines/utterance.py index 68c982f..e0ae5d6 100644 --- a/nirukta/timelines/utterance.py +++ b/nirukta/timelines/utterance.py @@ -29,7 +29,6 @@ LATIN_FONT, MISSING_CHUNK_RE, SANSKRIT_FONT, - SCALE, TYPST_CMD_RE, ALPHA_RE, WHITESPACE_RE, @@ -206,17 +205,17 @@ def construct(self): cursor += a[1] - a[0] - states[0].append(TypstText(set_font(sanskrit, SANSKRIT_FONT), scale=SCALE)) - states[1].append(TypstText(set_font(translit, LATIN_FONT), scale=SCALE)) - states[2].append(TypstText(set_font(english, LATIN_FONT), scale=SCALE)) + states[0].append(TypstText(set_font(sanskrit, SANSKRIT_FONT))) + states[1].append(TypstText(set_font(translit, LATIN_FONT))) + states[2].append(TypstText(set_font(english, LATIN_FONT))) for i in range(len(states[0])): # Start the transliteration in the center states[1][i].points.move_to(ORIGIN) # Move sa and en above and below - states[0][i].points.next_to(states[1][i], UP * SCALE) - states[2][i].points.next_to(states[1][i], DOWN * SCALE) + states[0][i].points.next_to(states[1][i], UP) + states[2][i].points.next_to(states[1][i], DOWN) # TODO: find a way to align on edges without making the english text move every expansion change # states[0][i].points.next_to( states[1][i], direction=UP * SCALE, aligned_edge=LEFT) diff --git a/nirukta/typst.py b/nirukta/typst.py index 3391eef..da6d996 100644 --- a/nirukta/typst.py +++ b/nirukta/typst.py @@ -1,7 +1,6 @@ from typing import List, Optional -# page_width = 266 height = 1.5 gutter = 0.2 From 2bdecd5f9b97e58aaae4930f0ad0a6ddc0d70fca Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Thu, 28 May 2026 17:58:41 -0400 Subject: [PATCH 42/50] ratios --- library/sUtrARi/vakratuRqa_mahAkAya.sutra | 2 +- main.py | 22 ++++++++++++---------- nirukta/render.py | 14 ++++++++------ nirukta/sloka.py | 11 +++-------- 4 files changed, 24 insertions(+), 25 deletions(-) diff --git a/library/sUtrARi/vakratuRqa_mahAkAya.sutra b/library/sUtrARi/vakratuRqa_mahAkAya.sutra index ef95d8e..d8a8676 100644 --- a/library/sUtrARi/vakratuRqa_mahAkAya.sutra +++ b/library/sUtrARi/vakratuRqa_mahAkAya.sutra @@ -8,6 +8,6 @@ vakra[curved]+tuRqa[trunk]=vakratuRqa mahA[large]+kAya[body]=mahAkAya sUrya[suns "and the brilliance equal to 10,000,000 suns..." --- line --- -(nir[free of]+viGnam[obstacles]=nirviGnam)=nirviGnaM kuru[make] me[for me] deva[god] sarva[all] kAryezu[in][endeavors] sarva[all]+dA[times]=sarvadA .. +(nir[free of]+viGnam[obstacles]=nirviGnam)=nirviGnaM kuru[make] me[for me] deva[god] sarva[all] kAryezu[in][endeavors] sarva[all]+dA[times]=sarvadA . 5 . "O god - make the way free of obstacles for me," "in all endeavors and all times." diff --git a/main.py b/main.py index 0336fb2..f5b25c6 100644 --- a/main.py +++ b/main.py @@ -16,20 +16,22 @@ chosen = choose_nirukta_file() -# During `janim run` (preview), render at half resolution to reduce GPU load. -# `janim write` (export) keeps full 1920×1080. -_preview_mode = "run" in sys.argv - - -class Nirukta(Timeline): - CONFIG = Config( +# Keep previews performant with HD exports +preview_mode = "run" in sys.argv +if preview_mode: + config = Config( fps=60, preview_fps=30, - pixel_width=960 if _preview_mode else 1920, - pixel_height=540 if _preview_mode else 1080, - anti_alias_width=0.001 if _preview_mode else 0.015, + pixel_width=960, + pixel_height=540, + anti_alias_width=0.001, ) +else: + config = Config(fps=60) + +class Nirukta(Timeline): + CONFIG = config nirukta: Timeline @property diff --git a/nirukta/render.py b/nirukta/render.py index 928bec7..b96ea56 100644 --- a/nirukta/render.py +++ b/nirukta/render.py @@ -193,12 +193,14 @@ def T(s): return f"#box[{inner}]" -def set_font(text: str, font: str, size: str = "11pt"): - return ( - f'#set text(font: "{font}", size: {size}, stroke: none)\n' - f"#set page(width: {266 * 1.3}pt)\n" - f"{text}" - ) +def set_font(text: str, font: str, ratio: float = 1.0, wrap: bool = False): + ratio *= 1.3 + result = f'#set text(font: "{font}", size: {ratio}em, stroke: none)\n' + if wrap: + result += f"#set page(width: {266 * 1.3 * ratio}pt)\n" + + result += f"{text}" + return result def text_box(text: str, color: str, stroke_mode: bool = False): diff --git a/nirukta/sloka.py b/nirukta/sloka.py index bde0a4e..c8c8505 100644 --- a/nirukta/sloka.py +++ b/nirukta/sloka.py @@ -126,7 +126,7 @@ def sloka_group_chandas( columns = len(padas[0]) ratio = min(Config.get.frame_width / columns, 1.0) - grid = TypstText(set_font(grid_code, font, f"{ratio}em")) + grid = TypstText(set_font(grid_code, font, ratio)) if blank: return Keyed(text=grid, keys=Group()) @@ -142,9 +142,7 @@ def title_and_pada_labels( # Position title and labels relative to the centered grid meter_deva = transform_text(meter_label, Language.SANSKRIT) title = TypstText( - set_font( - f"#text(fill: white, size: {ratio * 1.2}em)[{meter_deva}]", SANSKRIT_FONT - ), + set_font(meter_deva, SANSKRIT_FONT, ratio=ratio * 1.2), ) title.points.next_to(texttttt, UP) pada_labels = [transform_text(str(n), Language.SANSKRIT) for n in range(1, 5)] @@ -152,10 +150,7 @@ def title_and_pada_labels( for pada_idx, c_label in enumerate(labels): label_text = pada_labels[pada_idx] if pada_idx < len(pada_labels) else "" label = TypstText( - set_font( - f"#text(fill: white, size: {ratio}em)[{label_text}]", - SANSKRIT_FONT, - ), + set_font(label_text, SANSKRIT_FONT, ratio=ratio), ) label.points.next_to(texttttt.get_label(c_label), LEFT) labelz.append(label) From 39f3dd8c82c41888de04bb00106e2accb61b3e6d Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Thu, 28 May 2026 18:00:30 -0400 Subject: [PATCH 43/50] ratios --- nirukta/timelines/english.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nirukta/timelines/english.py b/nirukta/timelines/english.py index 06efb55..7633562 100644 --- a/nirukta/timelines/english.py +++ b/nirukta/timelines/english.py @@ -38,7 +38,7 @@ def construct(self): grid = arrange_vertical(list(map(lambda code: f"[{code}]", rows)), gutter=0.6) group = TypstText( - set_font(grid, LATIN_FONT), + set_font(grid, LATIN_FONT, wrap=True), ) self.play( From a818ff763f38dfac849025ad99fcf312b007bb28 Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Thu, 28 May 2026 19:00:19 -0400 Subject: [PATCH 44/50] missing sloka marker in stotram --- library/sUtrARi/SrIsarasvatIstotraM.sutra | 1 + 1 file changed, 1 insertion(+) diff --git a/library/sUtrARi/SrIsarasvatIstotraM.sutra b/library/sUtrARi/SrIsarasvatIstotraM.sutra index e7187af..8421e31 100644 --- a/library/sUtrARi/SrIsarasvatIstotraM.sutra +++ b/library/sUtrARi/SrIsarasvatIstotraM.sutra @@ -94,6 +94,7 @@ me[for me][my] sadA[always] .. 3 .. "the wife of Brahma, situated in a lotus seat. May Sarasvatī always dance for me in my speech." +=== sloka === --- line --- sarasvatI[Sarasvatī] From ac1265c56ab5248f926e024876e679da50978260 Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Fri, 29 May 2026 11:12:10 -0400 Subject: [PATCH 45/50] added syntax sugar and sandhi validation --- library/external.sloka | 9 +++ library/sUtrARi/SrIsarasvatIstotraM.sutra | 6 +- nirukta/parsing/grammars.py | 7 +- nirukta/parsing/visitors/sloka.py | 55 ++++++++++++-- nirukta/render.py | 6 ++ pyproject.toml | 1 + test_sandhi.py | 17 +++++ uv.lock | 90 +++++++++++++++++++++++ 8 files changed, 178 insertions(+), 13 deletions(-) create mode 100644 library/external.sloka create mode 100644 test_sandhi.py diff --git a/library/external.sloka b/library/external.sloka new file mode 100644 index 0000000..56e9ca4 --- /dev/null +++ b/library/external.sloka @@ -0,0 +1,9 @@ +=== sloka === + +--- line --- +(aRu[atomic]+rUpa[form]=aRurUpa)=aRurUpam +"atomic form!" + +--- line --- +aRu[atomic]+rUpa[form]=aRurUpa>>aRurUpam>>aRurUpaM +"atomic form!" diff --git a/library/sUtrARi/SrIsarasvatIstotraM.sutra b/library/sUtrARi/SrIsarasvatIstotraM.sutra index 8421e31..42db9eb 100644 --- a/library/sUtrARi/SrIsarasvatIstotraM.sutra +++ b/library/sUtrARi/SrIsarasvatIstotraM.sutra @@ -270,8 +270,8 @@ namaH[reverence]=namo namaH[reverence] .. 14 .. === sloka === --- line --- -aRu[atomic]+rUpa[form]=aRurUpe -mahA[large]+rUpa[form]=mahArUpe +aRu[atomic]+rUpa[form]=aRurUpa>>aRurUpe +mahA[large]+rUpa[form]=mahArUpa>>mahArUpe viSva[universal]+rUpa[form]=viSvarUpe namaH[reverence]=namo namaH[reverence] . "O you of atomic form, O you of large form, O you of universal form - reverence, reverence." @@ -350,7 +350,7 @@ namaH[reverence]=namo namaH[reverence] .. 19 .. sAyam[in the evening]=sAyaM prAtaH[in the morning] (paWen[should][recite]+nityam[daily]=paWennityam)=paWennityaM -(zAR[six]+mAsAt[months]=zARmAsAt)+(sidDih[attainment]+ucyate[It is said]=sidDirucyate)=zARmAsAtsidDirucyate . +(zAR[six]+mAsAt[months]=zARmAsAt)+(sidDiH[attainment]+ucyate[It is said]=sidDirucyate)=zARmAsAtsidDirucyate . "It is said that one should recite this daily in the evening and in the morning for six months to reach attainment." --- line --- diff --git a/nirukta/parsing/grammars.py b/nirukta/parsing/grammars.py index 19abc20..6e183b4 100644 --- a/nirukta/parsing/grammars.py +++ b/nirukta/parsing/grammars.py @@ -15,9 +15,10 @@ # Sandhi: one or more components joined by '+', then '=' surface form. # Components may themselves be parenthesised sandhi groups, enabling - # arbitrary nesting: (a[x]+b[y]=ab)+c[z]=abc - compound_token = comp_part plus_part* "=" slp1 etym_gloss? + # arbitrary nesting: (a[x]+b[y]=ab)+c[z]=abc>>abcd + compound_token = comp_part plus_part* "=" slp1 inflect_part* plus_part = "+" comp_part + inflect_part = ">>" slp1 comp_part = paren_compound / simple_token paren_compound = "(" compound_token ")" @@ -32,7 +33,7 @@ punct = ~r"\.+(?:\s*\d+\s*[.,;]*)?|[;,]" # SLP1: anything that isn't a format metacharacter or whitespace - slp1 = ~r"[^[\]{}.;=+()\"\s]+" + slp1 = ~r"[^[\]\>{}.;=+()\"\s]+" quoted_str = '"' ~r'(?:[^"\\]|\\.)*' '"' ws = ~r"\s*" diff --git a/nirukta/parsing/visitors/sloka.py b/nirukta/parsing/visitors/sloka.py index fda3463..2725837 100644 --- a/nirukta/parsing/visitors/sloka.py +++ b/nirukta/parsing/visitors/sloka.py @@ -1,12 +1,17 @@ import logging import os import traceback +from nirukta.render import transform_text, untransform_text +import sandhi as sandhi_module +from aksharamukha import transliterate from typing import Optional +from janim.imports import log from nirukta.inflection import Case, SanskritInflection from nirukta.models import ( CompoundToken, EnglishGloss, EtymGloss, + Language, Line, SimpleToken, Sloka, @@ -16,6 +21,7 @@ from nirukta.parsing.grammars import SLOKA_GRAMMAR from parsimonious.nodes import NodeVisitor +S = sandhi_module.Sandhi() class SlokaVisitor(NodeVisitor): file: str @@ -82,24 +88,58 @@ def visit_token(self, _, visited_children): # -- compound (sandhi) tokens ------------------------------------------- def visit_compound_token(self, _, visited_children): - first_part, plus_parts, _, surface, gloss = visited_children - etym_glosses = list(gloss) - etym_gloss: Optional[EtymGloss] = ( - etym_glosses[0] if len(etym_glosses) > 0 else None - ) + print(f"visited_children compound_token: {visited_children}") + first_part, plus_parts, _, surface, inflect_parts = visited_children parts = [first_part] + list(plus_parts) - return CompoundToken( + if len(parts) > 2: + log.warning(f"Cannot verify sandhi for more than 2 parts at once: {parts}") + elif len(parts) == 2: + A = transform_text(parts[0].slp1, Language.SANSKRIT) + B = transform_text(parts[1].slp1, Language.SANSKRIT) + C = transform_text(surface, Language.SANSKRIT) + + + results = S.sandhi(A, B) + + valid_forms = {untransform_text(r[0]) for r in results} + + A = untransform_text(A) + B = untransform_text(B) + C = untransform_text(C) + + is_valid = C in valid_forms + + if not is_valid: + log.warning(f"sandhi invalid: \t{A} + {B} != {C}\nvalid forms produces by this combination: {valid_forms}\n") + else: + log.info(f"sandhi verified:\t{A} + {B} = {C}") + + # normalize inflect_parts + i_parts = inflect_parts if isinstance(inflect_parts, list) else [] + i_parts = [p for p in i_parts if isinstance(p, str)] + + # fold left: each >> wraps the previous result in a new CompoundToken + result = CompoundToken( parts=parts, slp1=surface, - etym_gloss=etym_gloss, ) + for slp1 in i_parts: + result = CompoundToken(parts=[result], slp1=slp1) + + return result + + def visit_plus_part(self, _, visited_children): _, part = visited_children return part + def visit_inflect_part(self, _, visited_children): + _, slp1 = visited_children + return slp1 + def visit_comp_part(self, _, visited_children): return visited_children[0] @@ -107,6 +147,7 @@ def visit_paren_compound(self, _, visited_children): _, compound, _ = visited_children return compound + # -- simple tokens & glosses -------------------------------------------- def visit_simple_token(self, _, visited_children): diff --git a/nirukta/render.py b/nirukta/render.py index b96ea56..cee385b 100644 --- a/nirukta/render.py +++ b/nirukta/render.py @@ -244,6 +244,12 @@ def transform_text(text: str, language: Language): raise ValueError(f'Cannot represent "{text}" in devanagari') return deva +def untransform_text(text: str): + iast = transliterate.process("DEVANAGARI", "IAST", text) + if not iast: + raise ValueError(f'Cannot represent "{text}" in IAST') + return iast + def typst_code( text: str, language: Language, color: str = WHITE, stroke_mode: bool = False diff --git a/pyproject.toml b/pyproject.toml index 83efc3c..ab4207f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,6 +9,7 @@ dependencies = [ "dill>=0.4.1", "janim[gui]>=4.2.0", "parsimonious>=0.11.0", + "sandhi>=1.0.0", "skrutable>=2.6.3", ] diff --git a/test_sandhi.py b/test_sandhi.py new file mode 100644 index 0000000..b110759 --- /dev/null +++ b/test_sandhi.py @@ -0,0 +1,17 @@ +import sandhi as sandhi_module +S = sandhi_module.Sandhi() + +A = "रामः" +B = "च" +C = "रामश्च" + +results = S.sandhi(A, B) +# results is a list of [output_form, rule_chain, rule_names] + +valid_forms = {r[0] for r in results} +print(valid_forms) +# see all valid sandhi results + +is_valid = C in valid_forms +print(is_valid) +# True or False diff --git a/uv.lock b/uv.lock index a65cd00..0093c1a 100644 --- a/uv.lock +++ b/uv.lock @@ -21,6 +21,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5b/07/63495f4fb3be0a84025bdac9469a8737e1b71668c64bedfb77b3d160efbe/aksharamukha-2.3-py3-none-any.whl", hash = "sha256:aaaccff78c2ecfaa1e39df12c29eeecbe9dece19508221347855974d1d3e2446", size = 289929, upload-time = "2024-10-14T20:36:31.215Z" }, ] +[[package]] +name = "annotated-doc" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, +] + [[package]] name = "attrs" version = "26.1.0" @@ -30,6 +39,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, ] +[[package]] +name = "backports-functools-lru-cache" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/dc/a1962ae19f83d2a92b8ac76ad2dfdeccf2748464f1560a51d343b9e2609a/backports.functools_lru_cache-2.0.0.tar.gz", hash = "sha256:dcbfa5e0dae8a014168807c9e026d33eead71df5af76c1fb78fd248bf07f6f99", size = 11578, upload-time = "2023-12-13T22:34:02.998Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/c6/4761a2ccb03d650ca803b11a7cdd69ff0696926d3fea218c8ca22c808448/backports.functools_lru_cache-2.0.0-py2.py3-none-any.whl", hash = "sha256:0a754323a46847735a112677fb8807b45f6d824d02a5795a50905218ac56a0d6", size = 6657, upload-time = "2023-12-13T22:34:00.587Z" }, +] + [[package]] name = "beautifulsoup4" version = "4.14.3" @@ -235,6 +253,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, ] +[[package]] +name = "indic-transliteration" +version = "2.3.82" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "backports-functools-lru-cache" }, + { name = "regex" }, + { name = "roman" }, + { name = "toml" }, + { name = "tqdm" }, + { name = "typer" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/c1/65ae96680758615e042415fb1d3a1e573c2419387205135501d96be97bb8/indic_transliteration-2.3.82-py3-none-any.whl", hash = "sha256:243b8a444f14c1a811c03ba07e8aa300da61a7c4172a45556fdce1d963038019", size = 162886, upload-time = "2026-04-06T10:48:04.426Z" }, +] + [[package]] name = "jaconv" version = "0.5.0" @@ -450,6 +484,7 @@ dependencies = [ { name = "dill" }, { name = "janim", extra = ["gui"] }, { name = "parsimonious" }, + { name = "sandhi" }, { name = "skrutable" }, ] @@ -459,6 +494,7 @@ requires-dist = [ { name = "dill", specifier = ">=0.4.1" }, { name = "janim", extras = ["gui"], specifier = ">=4.2.0" }, { name = "parsimonious", specifier = ">=0.11.0" }, + { name = "sandhi", specifier = ">=1.0.0" }, { name = "skrutable", specifier = ">=2.6.3" }, ] @@ -846,6 +882,36 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/87/2a/a1810c8627b9ec8c57ec5ec325d306701ae7be50235e8fd81266e002a3cc/rich-14.3.1-py3-none-any.whl", hash = "sha256:da750b1aebbff0b372557426fb3f35ba56de8ef954b3190315eb64076d6fb54e", size = 309952, upload-time = "2026-01-24T21:40:42.969Z" }, ] +[[package]] +name = "roman" +version = "5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/7c/3901b35ed856329bf98e84da8e5e0b4d899ea0027eee222f1be42a24ff3f/roman-5.2.tar.gz", hash = "sha256:275fe9f46290f7d0ffaea1c33251b92b8e463ace23660508ceef522e7587cb6f", size = 8185, upload-time = "2025-11-11T08:03:57.025Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/14/ea3cdd7276fcd731a9003fe4abeb6b395a38110ddff6a6a509f4ee00f741/roman-5.2-py3-none-any.whl", hash = "sha256:89d3b47400388806d06ff77ea77c79ab080bc127820dea6bf34e1f1c1b8e676e", size = 6041, upload-time = "2025-11-11T08:03:56.051Z" }, +] + +[[package]] +name = "sandhi" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "indic-transliteration" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ee/84/4a5d1851a6097b513b8dab1a1a78c59a1b617507df22cd12a86aa703e123/sandhi-1.0.0.tar.gz", hash = "sha256:31a259bcfc330289907f2bba08a0e09ba766059a2e6d8f54d4e7d4a5e038aff8", size = 42883, upload-time = "2025-12-10T16:48:28.204Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/da/9c25a70d47cadc35a71ef552cf53212fa35b8ff3291960d55d33fa358a86/sandhi-1.0.0-py2.py3-none-any.whl", hash = "sha256:4b4651184ba6a93fad893e25c2a874dd64f41eee11f7357d7f452b5128f93968", size = 31432, upload-time = "2025-12-10T16:48:26.385Z" }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + [[package]] name = "shiboken6" version = "6.11.0" @@ -920,6 +986,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/48/2c/6c9bb53db56c8a12a736d2158a8b842a5993b96daabc29d90a098e840280/svgelements-1.9.6-py2.py3-none-any.whl", hash = "sha256:8a5cf2cc066d98e713d5b875b1d6e5eeb9b92e855e835ebd7caab2713ae1dcad", size = 137856, upload-time = "2023-08-17T02:01:48.76Z" }, ] +[[package]] +name = "toml" +version = "0.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253, upload-time = "2020-11-01T01:40:22.204Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" }, +] + [[package]] name = "tqdm" version = "4.67.1" @@ -932,6 +1007,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, ] +[[package]] +name = "typer" +version = "0.26.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "rich" }, + { name = "shellingham" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cc/15/f5fc7be23b7196bc065b282d9589a372392fb10860c80f9c1dd7eb008662/typer-0.26.3.tar.gz", hash = "sha256:3e2b9352f535e5303ef27806dadc2c8647687bdca5c902f03fec3fb88f46a46a", size = 198326, upload-time = "2026-05-28T20:30:50.984Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/cc/c6c5dea061e2740355bfeef22ac6a41751bd2f3903e83921295569bdcec4/typer-0.26.3-py3-none-any.whl", hash = "sha256:e70549ec5a403ca8a0bf0802ddd9f3c6ff7a14ccbb859b01b697baa943636f33", size = 122338, upload-time = "2026-05-28T20:30:49.816Z" }, +] + [[package]] name = "typing-extensions" version = "4.15.0" From d3a4404c90ecc62ff4abe7f668ee01795bc6637b Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Fri, 29 May 2026 11:17:10 -0400 Subject: [PATCH 46/50] validation script --- nirukta/parsing/visitors/sloka.py | 2 -- validate.py | 9 +++++++++ 2 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 validate.py diff --git a/nirukta/parsing/visitors/sloka.py b/nirukta/parsing/visitors/sloka.py index 2725837..15e50c8 100644 --- a/nirukta/parsing/visitors/sloka.py +++ b/nirukta/parsing/visitors/sloka.py @@ -88,7 +88,6 @@ def visit_token(self, _, visited_children): # -- compound (sandhi) tokens ------------------------------------------- def visit_compound_token(self, _, visited_children): - print(f"visited_children compound_token: {visited_children}") first_part, plus_parts, _, surface, inflect_parts = visited_children parts = [first_part] + list(plus_parts) @@ -165,7 +164,6 @@ def visit_etym_gloss(self, _, visited_children): _, content, _ = visited_children try: inflection = SanskritInflection.parse(content) - print(f"inflection: {inflection}") return inflection except Exception: try: diff --git a/validate.py b/validate.py new file mode 100644 index 0000000..a9b2440 --- /dev/null +++ b/validate.py @@ -0,0 +1,9 @@ +from nirukta.util import SlokaVisitor, SutraVisitor, choose_nirukta_file, is_nirukta_file + +chosen = choose_nirukta_file() +assert is_nirukta_file(chosen), "Invalid file" + +if ".sutra" in chosen: + SutraVisitor(chosen).parse() +else: + SlokaVisitor(chosen).parse() From 230aa92c334658a7e9f1ee411ee20620ce590f6b Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Fri, 29 May 2026 14:28:23 -0400 Subject: [PATCH 47/50] validating sutra --- library/sUtrARi/SrIsarasvatIstotraM.sutra | 109 +++++++++++----------- nirukta/parsing/visitors/sloka.py | 12 +-- validate.py | 17 +++- 3 files changed, 71 insertions(+), 67 deletions(-) diff --git a/library/sUtrARi/SrIsarasvatIstotraM.sutra b/library/sUtrARi/SrIsarasvatIstotraM.sutra index 42db9eb..96d4044 100644 --- a/library/sUtrARi/SrIsarasvatIstotraM.sutra +++ b/library/sUtrARi/SrIsarasvatIstotraM.sutra @@ -31,7 +31,7 @@ Sveta[white]+padma[lotus]+AsanA[seated]=SvetapadmAsanA . --- line --- yA[She who] -(brahma[Brahma]+acyuta[Viṣṇu]+SaNkara[Śiva]+praBftiBiH[and other]+deva[devas]=brahmAcyutaSaNkarapraBftiBirdeva)+sadA[always]=brahmAcyutaSaNkarapraBftiBirdevEssadA +(brahma[Brahma]+acyuta[Viṣṇu]+SaNkara[Śiva]+praBftiBiH[and other]+deva[devas]=brahmAcyutaSaNkarapraBftiBirdeva>>brahmAcyutaSaNkarapraBftiBirdevEH)+sadA[always]=brahmAcyutaSaNkarapraBftiBirdevEssadA pUjitA[worshipped] "She who is always worshipped by Brahma, Viṣṇu, Śiva, and other devas." @@ -69,7 +69,7 @@ BAsamAnA[Shining]+asamAnA[incomparable]=BAsamAnA'samAnA --- line --- sA[she] me[my] -(vAc[speech]+devatA[Goddess]+iyam[this]=vAgdevateyam)=vAgdevateyaM +vAc[speech]+devatA[Goddess]+iyam[this]=vAgdevateyam>>vAgdevateyaM nivasatu[may][reside] vadan[in][mouth]=vadane sarvadA[always] @@ -114,13 +114,13 @@ vara[boons]+prasAdinI[gracious]=varaprasAdinI .. 4 .. --- line --- sarasvati[O Sarasvatī] -(namas[reverence]+tuByam[to you]=namastuByam)=namastuByaM +(namas[reverence]=namaH)+tuByam[to you]=namastuByam>>namastuByaM varade[boon-giver] kAmarUpiRi[form of longing] . "O Sarasvatī, boon-giver, the very form of longing; reverence to you." --- line --- -(vidyA[learning]+AramBam[beginning]=vidyAramBam)=vidyAramBaM +vidyA[learning]+AramBam[beginning]=vidyAramBam>>vidyAramBaM karizyAmi[I will undertake] "I will undertake the beginning of learning;" @@ -133,7 +133,7 @@ sadA[always] .. 5 .. --- line --- sarasvati[O Sarasvatī] -(namas[reverence]+tuByam[to you]=namastuByam)=namastuByaM +(namas[reverence]=namaH)+tuByam[to you]=namastuByam>>namastuByaM "O Sarasvatī, reverence to you." sarva[all]+devi[O Goddess]=sarvadevi @@ -141,38 +141,38 @@ namaH[reverence]=namo namaH[reverence] . "O Goddess of all, reverence, reverence." --- line --- -SAnta[tranquil]+rupa[form]=SAntarUpe +SAnta[tranquil]+rUpa[form]=SAntarUpa>>SAntarUpe SaSi[moon]+Dare[bearer]=SaSiDare -sarva[all]+yoga[disciplines]=sarvayoge +sarva[all]+yoga[disciplines]=sarvayoga>>sarvayoge namaH[reverence]=namo namaH[reverence] .. 6 .. "O you of tranquil form, O bearer of the moon, O you in whom all disciplines reside, reverence, reverence." === sloka === --- line --- -nitya[constant]+Ananda[in][bliss]=nityAnande -nir[un]+ADara[supported]=nirADAre +nitya[constant]+Ananda[in][bliss]=nityAnanda>>nityAnande +nir[un]+ADAra[supported]=nirADAra>>nirADAre nizkala[undivided]=nizkalAyE namaH[reverence]=namo namaH[reverence] . "O you in constant bliss, O unsupported one - reverence, reverence to her who is undivided." --- line --- -vidyA[knowledge]+Dara[bearer]=vidyADare +vidyA[knowledge]+Dara[bearer]=vidyADara>>vidyADare viSA[large]+lAkzi[eyed]=viSAlAkzi -SudDa[pure]+jYana[knowledge]=SudDajYAne +SudDa[pure]+jYAna[knowledge]=SudDajYAna>>SudDajYAne namaH[reverence]=namo namaH[reverence] .. 7 .. "O bearer of knowledge, O large eyed one, O embodier of pure knowledge, reverence, reverence." === sloka === --- line --- -(SudDa[pure]+sPawika[crystal]+rUpa[form]=SudDasPawikarUpa)=SudDasPawikarUpAyE -sUkzma[subtle]+rupa[form]=sUkzmarUpe +SudDa[pure]+sPawika[crystal]+rUpa[form]=SudDasPawikarUpa>>SudDasPawikarUpAyE +sUkzma[subtle]+rUpa[form]=sUkzmarUpa>>sUkzmarUpe namaH[reverence]=namo namaH[reverence] . "O you of subtle form - reverence, reverence to her of pure crystal form." --- line --- -Sabda[Sabda]+brahma[Brahman]=Sabdabrahmi -catur[four]+hasta[handed]=caturhaste sarva[all]+sidDa[accomplishment]=sarvasidDyE +Sabda[Sabda]+brahma[Brahman]=Sabdabrahma>>Sabdabrahmi +catur[four]+hasta[handed]=caturhasta>>caturhaste sarva[all]+sidDa[accomplishment]=sarvasidDa>>sarvasidDyE namaH[reverence]=namo namaH[reverence] .. 8 .. "O you who are Sabda Brahman, O four-handed one - reverence, reverence to her who is all accomplishment." @@ -185,24 +185,24 @@ namaH[reverence]=namo namaH[reverence] . "O you who are the root chakra - reverence, reverence to her whose every limb is adorned by pearls." --- line --- -mUla[basis]+mantra[mantra]+svarUpa[self-form]=mUlamantrasvarUpAyE -mUla[origin]+Sakta[ability]=mUlaSaktyE +mUla[basis]+mantra[mantra]+svarUpa[self-form]=mUlamantrasvarUpa>>mUlamantrasvarUpAyE +mUla[origin]+Sakta[ability]=mUlaSakta>>mUlaSaktyE namaH[Reverence]=namo namaH[reverence] .. 9 .. "Reverence, reverence to her whose self-form is the basis of mantra itself, to her who is the origin of ability." === sloka === --- line --- -manas[mind]+mayi[consist]=manomayi -mahA[great]+yoga[yoga]=mahAyoge -vAc[speech]+ISvarI[Goddess]=vAgISvari +(manas[mind]=manaH)+mayi[consist]=manomayi +mahA[great]+yoga[yoga]=mahAyoga>>mahAyoge +vAk[speech]+ISvarI[Goddess]=vAgISvarI>>vAgISvari namaH[reverence]=namo namaH[reverence] . "O you who consist of the mind, O embodier of great yoga, O Goddess of speech, reverence, reverence." --- line --- vARa[music]=vARyE vara[boon]+da[giving]+hasta[hand]=varadahastAyE -vara[boon]+dA[giver]=varadAyE +vara[boon]+dA[giver]=varadA>>varadAyE namaH[Reverence]=namo namaH[reverence] .. 10 .. "Reverence, reverence to the embodiment of music, to her who extends a boon-giving hand, to the giver of boons." @@ -210,23 +210,23 @@ namaH[Reverence]=namo namaH[reverence] .. 10 .. --- line --- veda[knowledge]=vedyAyE -veda[knowledge]+rUpa[form]=vedarUpAyE -veda[Vedas]+antara[culmination]=vedAntAyE +veda[knowledge]+rUpa[form]=vedarUpa>>vedarUpAyE +veda[Vedas]+anta[culmination]=vedAnta>>vedAntAyE namaH[Reverence]=namo namaH[reverence] . "Reverence, reverence to her who is knowledge itself, to her in the form of knowledge, to her who is the culmination of all the Vedas." --- line --- guRa[virtues]+doza[faults]+vivarjita[free from]=guRadozavivarjinyE -guRa[qualities]+dIpta[shining]=guRadIptyE +guRa[qualities]+dIpta[shining]=guRadIpta>>guRadIptyE namaH[Reverence]=namo namaH[reverence] .. 11 .. "Reverence, reverence to her who is free from virtues and faults, to her of shining qualities." === sloka === --- line --- -sarva[all]+jYAna[knowledge]=sarvajYAne -sadA[always]+Ananda[bliss]=sadAnande -sarva[all]+rUpa[form]=sarvarUpe +sarva[all]+jYAna[knowledge]=sarvajYAna>>sarvajYAne +sadA[always]+Ananda[bliss]=sadAnanda>>sadAnande +sarva[all]+rUpa[form]=sarvarUpa>>sarvarUpe namaH[reverence]=namo namaH[reverence] . "O you who are all knowledge, O you who are always in bliss, O you who are all form - reverence, reverence." @@ -240,16 +240,16 @@ namaH[reverence]=namo namaH[reverence] .. 12 .. === sloka === --- line --- -yoga[yoga]+rUpa[form]=yogarUpe -ramA[Lakṣmī]+devI[Goddess]=ramAdevyE -yoga[yoga]+Ananda[bliss]=yogAnande +yoga[yoga]+rUpa[form]=yogarUpa>>yogarUpe +ramA[Lakṣmī]+devI[Goddess]=ramAdevI>>ramAdevyE +yoga[yoga]+Ananda[bliss]=yogAnanda>>yogAnande namaH[reverence]=namo namaH[reverence] . "O you who are the form of yoga, O embodiment of the bliss of yoga - reverence, reverence to that Goddess Lakṣmī." --- line --- -divya[divine]+jYA[knower]=divyajYAyE -tri[three]+netra[eyed]=trinetrAyE -divya[divine]+mUrtI[embodiment]=divyamUrtyE +divya[divine]+jYA[knower]=divyajYA>>divyajYAyE +tri[three]+netra[eyed]=trinetra>>trinetrAyE +divya[divine]+mUrtI[embodiment]=divyamUrtI>>divyamUrtyE namaH[Reverence]=namo namaH[reverence] .. 13 .. "Reverence, reverence to the knower of the divine, to the three-eyed one, to she who has divine embodiment." @@ -257,13 +257,13 @@ namaH[Reverence]=namo namaH[reverence] .. 13 .. --- line --- arDa[half]+candra[moon]+jawA[twisted hair]+DAra[wears]=arDacandrajawADAri -candra[moon]+bimba[reflection]=candrabimbe +candra[moon]+bimba[reflection]=candrabimba>>candrabimbe namaH[reverence]=namo namaH[reverence] . "O you who wears the half moon in twisted hair, O reflection of the moon - reverence, reverence." --- line --- candra[moon]+aditya[sun]+jawA[twisted hair]+DAra[wear]=candrAdityajawADAri -candra[moon]+bimba[reflection]=candrabimbe +candra[moon]+bimba[reflection]=candrabimba>>candrabimbe namaH[reverence]=namo namaH[reverence] .. 14 .. "O you who wear the sun and moon in twisted hair, O reflection of the moon - reverence, reverence." @@ -272,7 +272,7 @@ namaH[reverence]=namo namaH[reverence] .. 14 .. --- line --- aRu[atomic]+rUpa[form]=aRurUpa>>aRurUpe mahA[large]+rUpa[form]=mahArUpa>>mahArUpe -viSva[universal]+rUpa[form]=viSvarUpe +viSva[universal]+rUpa[form]=viSvarUpa>>viSvarUpe namaH[reverence]=namo namaH[reverence] . "O you of atomic form, O you of large form, O you of universal form - reverence, reverence." @@ -286,45 +286,45 @@ namaH[Reverence]=namo namaH[reverence] .. 15 .. --- line --- jYAna[knowledge]+vijYana[wisdom]+rUpa[form]=jYAnavijYAnarUpAyE -jYAna[knowledge]+mURtI[embodiment]=jYAnamUrte +jYAna[knowledge]+mUrtI[embodiment]=jYAnamUrtI>>jYAnamUrte namaH[reverence]=namo namaH[reverence] . "O embodiment of knowledge - reverence, reverence to you who are the form of knowledge and wisdom." --- line --- nAna[various]+SAstra[scriptures]+svarUpa[nature]=nAnASAstrasvarUpAyE -nAnA[many]+rUpa[forms]=nAnArUpe +nAnA[many]+rUpa[forms]=nAnArUpa>>nAnArUpe namaH[reverence]=namo namaH[reverence] .. 16 .. "O you of many forms - reverence, reverence to her who is the true nature of various scriptures." === sloka === --- line --- -padma[lotus]+da[giver]=padmade -padma[lotus]+vaMSa[lineage]=padmavaMSe +padma[lotus]+da[giver]=padmada>>padmade +padma[lotus]+vaMSa[lineage]=padmavaMSa>>padmavaMSe ca[and] -padma[lotus]+rUpa[form]=padmarUpe +padma[lotus]+rUpa[form]=padmarUpa>>padmarUpe namaH[reverence]=namo namaH[reverence] . "O lotus-giver, O you of the lotus lineage and you in the form of a lotus - reverence, reverence." --- line --- paramezWyE[supreme Goddess] -parA[supreme]+mUrtI[embodiment]=parAmUrtyE -namas[reverence]+te[to you]=namaste +parA[supreme]+mUrtI[embodiment]=parAmUrtI>>parAmUrtyE +namaH[reverence]+te[to you]=namaste pApa[sins]+nASinI[destroyer]=pApanASinI .. 17 .. "O destroyer of sins - reverence to you, supreme Goddess of supreme embodiment." === sloka === --- line --- -mahA[great]+devI[Goddess]=mahAdevyE -mahA[great]+kAlI[Kālī]=mahAkAlyE -mahA[great]+lakzmI[Lakṣmī]=mahAlakzmyE +mahA[great]+devI[Goddess]=mahAdevI>>mahAdevyE +mahA[great]+kAlI[Kālī]=mahAkAlI>>mahAkAlyE +mahA[great]+lakzmI[Lakṣmī]=mahAlakzmI>>mahAlakzmyE namaH[Reverence]=namo namaH[reverence] . "Reverence, reverence to the great Goddess, to the great Kālī, to the great Lakṣmī" --- line --- brahma[Brahma]+vizRu[Viṣṇu]+Siva[Śiva]+AKyA[known as]=brahmavizRuSivAKyAyE -brahma[divine]+nArI[woman]=brahmanAryE +brahma[divine]+nArI[woman]=brahmanArI>>brahmanAryE namaH[Reverence]=namo namaH[reverence] .. 18 .. "Reverence, reverence to she who is known as Brahman, Viṣṇu, Śiva, to the divine woman." @@ -333,14 +333,14 @@ namaH[Reverence]=namo namaH[reverence] .. 18 .. --- line --- kamala[lotus]+Akara[source]+puzpA[flower]=kamalAkarapuzpA ca[and] -kAma[desire]+rUpa[the form]=kAmarUpe +kAma[desire]+rUpa[the form]=kAmarUpa>>kAmarUpe namaH[reverence]=namo namaH[reverence] . "O source of the lotus flower and O you who takes the form that you desire - reverence, reverence." --- line --- kapAli[skull-bearer] -karma[actions]+dIpta[shine]=karmadIptAyE -karma[action]+dA[giver]=karmadAyE +karma[actions]+dIpta[shine]=karmadIpta>>karmadIptAyE +karma[action]+dA[giver]=karmadA>>karmadAyE namaH[reverence]=namo namaH[reverence] .. 19 .. "O skull-bearer - reverence, reverence to the giver of action, to she whose actions shine." @@ -349,12 +349,12 @@ namaH[reverence]=namo namaH[reverence] .. 19 .. --- line --- sAyam[in the evening]=sAyaM prAtaH[in the morning] -(paWen[should][recite]+nityam[daily]=paWennityam)=paWennityaM +paWen[should][recite]+nityam[daily]=paWennityam>>paWennityaM (zAR[six]+mAsAt[months]=zARmAsAt)+(sidDiH[attainment]+ucyate[It is said]=sidDirucyate)=zARmAsAtsidDirucyate . "It is said that one should recite this daily in the evening and in the morning for six months to reach attainment." --- line --- -((cora[thieves]+vyAGra[tigers]=coravyAGra)+Baya[fear]=coravyAGraBayam)=coravyAGraBayaM +(cora[thieves]+vyAGra[tigers]=coravyAGra)+Baya[fear]=coravyAGraBaya>>coravyAGraBayam>>coravyAGraBayaM na[not]+asti[exist]=nAsti paWatAm[those who recite]=paWatAM SfRvatAm[those who listen]+api[Also]=SfRvatAmapi .. 20 .. @@ -368,7 +368,7 @@ sarasvatI[Sarasvatī]+stotra[stotra]+agastya[Agastya]+muni[sage]+vAcaka[the spea "Thus, the speaking of sage Agastya's Sarasvatī stotram" --- line --- -(sarva[all]+sidDi[skills]+kara[production]=sarvasidDikaram)=sarvasidDikaraM +sarva[all]+sidDi[skills]+kara[production]=sarvasidDikaram>>sarvasidDikaraM nFRAM[leads to] sarva[all]+pApa[sins]+praRASana[destruction]=sarvapApapraRASanam .. 21 .. "leads to the production of all skills and the destruction of all sins." @@ -376,5 +376,6 @@ sarva[all]+pApa[sins]+praRASana[destruction]=sarvapApapraRASanam .. 21 .. === sloka === --- line --- -.. (iti[Thus]+(agastya[Agastya]+muni[sage]=agastyamuni)+proktam[spoken]=ityagastyamuniproktam)=ityagastyamuniproktaM (sarasvatI[Sarasvatī]+stotra[stotra]=sarasvatIstotram)=sarasvatIstotraM sampUrRam[complete] .. +.. iti[Thus]+(agastya[Agastya]+muni[sage]=agastyamuni)+proktam[spoken]=ityagastyamuniproktam>>ityagastyamuniproktaM +sarasvatI[Sarasvatī]+stotra[stotra]=sarasvatIstotra>>sarasvatIstotram>>sarasvatIstotraM sampUrRam[complete] .. "Thus, the Sarasvatī stotra spoken by sage Agastya is complete." diff --git a/nirukta/parsing/visitors/sloka.py b/nirukta/parsing/visitors/sloka.py index 15e50c8..3f8a4d8 100644 --- a/nirukta/parsing/visitors/sloka.py +++ b/nirukta/parsing/visitors/sloka.py @@ -3,14 +3,11 @@ import traceback from nirukta.render import transform_text, untransform_text import sandhi as sandhi_module -from aksharamukha import transliterate -from typing import Optional from janim.imports import log from nirukta.inflection import Case, SanskritInflection from nirukta.models import ( CompoundToken, EnglishGloss, - EtymGloss, Language, Line, SimpleToken, @@ -23,6 +20,7 @@ S = sandhi_module.Sandhi() + class SlokaVisitor(NodeVisitor): file: str dir: str @@ -43,7 +41,6 @@ def parse(self) -> SlokaFile: tree = SLOKA_GRAMMAR.parse(self.source) return self.visit(tree) - # -- top level ---------------------------------------------------------- def visit_sloka(self, _, visited_children): @@ -99,7 +96,6 @@ def visit_compound_token(self, _, visited_children): B = transform_text(parts[1].slp1, Language.SANSKRIT) C = transform_text(surface, Language.SANSKRIT) - results = S.sandhi(A, B) valid_forms = {untransform_text(r[0]) for r in results} @@ -111,7 +107,9 @@ def visit_compound_token(self, _, visited_children): is_valid = C in valid_forms if not is_valid: - log.warning(f"sandhi invalid: \t{A} + {B} != {C}\nvalid forms produces by this combination: {valid_forms}\n") + log.warning( + f"sandhi invalid: \t{A} + {B} != {C}\nvalid forms produces by this combination: {valid_forms}\n" + ) else: log.info(f"sandhi verified:\t{A} + {B} = {C}") @@ -130,7 +128,6 @@ def visit_compound_token(self, _, visited_children): return result - def visit_plus_part(self, _, visited_children): _, part = visited_children return part @@ -146,7 +143,6 @@ def visit_paren_compound(self, _, visited_children): _, compound, _ = visited_children return compound - # -- simple tokens & glosses -------------------------------------------- def visit_simple_token(self, _, visited_children): diff --git a/validate.py b/validate.py index a9b2440..158e5aa 100644 --- a/validate.py +++ b/validate.py @@ -1,9 +1,16 @@ -from nirukta.util import SlokaVisitor, SutraVisitor, choose_nirukta_file, is_nirukta_file +from nirukta.util import ( + SlokaVisitor, + SutraVisitor, + choose_nirukta_file, + is_nirukta_file, +) chosen = choose_nirukta_file() assert is_nirukta_file(chosen), "Invalid file" -if ".sutra" in chosen: - SutraVisitor(chosen).parse() -else: - SlokaVisitor(chosen).parse() +while True: + if ".sutra" in chosen: + SutraVisitor(chosen).parse() + else: + SlokaVisitor(chosen).parse() + input("validate again?") From d2dbbd6e1eebffe6c50780f530898e479f0b47ce Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Fri, 29 May 2026 15:11:34 -0400 Subject: [PATCH 48/50] experimenting with multiwordsandhichecking --- nirukta/parsing/visitors/sloka.py | 45 ++++++++++++++++++++++++++++--- nirukta/render.py | 3 +++ validate.py | 4 ++- 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/nirukta/parsing/visitors/sloka.py b/nirukta/parsing/visitors/sloka.py index 3f8a4d8..f40a215 100644 --- a/nirukta/parsing/visitors/sloka.py +++ b/nirukta/parsing/visitors/sloka.py @@ -2,6 +2,7 @@ import os import traceback from nirukta.render import transform_text, untransform_text +from nirukta.strings import unswara import sandhi as sandhi_module from janim.imports import log from nirukta.inflection import Case, SanskritInflection @@ -90,11 +91,47 @@ def visit_compound_token(self, _, visited_children): parts = [first_part] + list(plus_parts) if len(parts) > 2: - log.warning(f"Cannot verify sandhi for more than 2 parts at once: {parts}") + log.info("meow") + """ built = "" + for i in range(len(parts)): + # A = transform_text(parts[i].slp1, Language.SANSKRIT) + A = built + B = transform_text(unswara(parts[i].slp1), Language.SANSKRIT) + log.info(f"adding: {untransform_text(A)} + {untransform_text(B)}") + + results = S.sandhi(A, B) + valid_forms = {untransform_text(r[0]) for r in results} + log.info(f"valid_forms: {valid_forms}") + compact_results = list(filter(lambda x: " " not in x, valid_forms)) + compact_results = list( + map(lambda x: x.replace("_", ""), compact_results) + ) + + if len(compact_results) > 1 or len(compact_results) == 0: + log.warning(f"cannot verify: {compact_results}") + else: + log.info(f"compact: {compact_results}") + built = compact_results[0] + + final_result = transform_text(unswara(surface), Language.SANSKRIT) + final_result = untransform_text(final_result) + built = untransform_text(built) + undone_parts = list( + map(lambda x: transform_text(x.slp1, Language.TRANSLIT), parts) + ) + + if built != final_result: + log.warning( + f"unable for verify sandhi for these parts: {undone_parts}\n" + f"expected {final_result} but got {built}" + ) + else: + log.info(f"sandhi verified:\t{undone_parts} = {final_result}") """ + elif len(parts) == 2: - A = transform_text(parts[0].slp1, Language.SANSKRIT) - B = transform_text(parts[1].slp1, Language.SANSKRIT) - C = transform_text(surface, Language.SANSKRIT) + A = transform_text(unswara(parts[0].slp1), Language.SANSKRIT) + B = transform_text(unswara(parts[1].slp1), Language.SANSKRIT) + C = transform_text(unswara(surface), Language.SANSKRIT) results = S.sandhi(A, B) diff --git a/nirukta/render.py b/nirukta/render.py index cee385b..7ce2a33 100644 --- a/nirukta/render.py +++ b/nirukta/render.py @@ -244,7 +244,10 @@ def transform_text(text: str, language: Language): raise ValueError(f'Cannot represent "{text}" in devanagari') return deva + def untransform_text(text: str): + if text == "": + return "" iast = transliterate.process("DEVANAGARI", "IAST", text) if not iast: raise ValueError(f'Cannot represent "{text}" in IAST') diff --git a/validate.py b/validate.py index 158e5aa..dc7c98a 100644 --- a/validate.py +++ b/validate.py @@ -13,4 +13,6 @@ SutraVisitor(chosen).parse() else: SlokaVisitor(chosen).parse() - input("validate again?") + c = input("validate again? y/n") + if c == "n": + break From 271a301d1d5f3074e2f8b1f0b309c1275e585e2b Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Fri, 29 May 2026 15:24:49 -0400 Subject: [PATCH 49/50] fix alignment fix wrap --- nirukta/render.py | 2 +- nirukta/timelines/utterance.py | 23 +++++++++++++---------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/nirukta/render.py b/nirukta/render.py index 7ce2a33..1f2fd6d 100644 --- a/nirukta/render.py +++ b/nirukta/render.py @@ -197,7 +197,7 @@ def set_font(text: str, font: str, ratio: float = 1.0, wrap: bool = False): ratio *= 1.3 result = f'#set text(font: "{font}", size: {ratio}em, stroke: none)\n' if wrap: - result += f"#set page(width: {266 * 1.3 * ratio}pt)\n" + result += f"#set page(width: {266 * ratio}pt)\n" result += f"{text}" return result diff --git a/nirukta/timelines/utterance.py b/nirukta/timelines/utterance.py index e0ae5d6..80da430 100644 --- a/nirukta/timelines/utterance.py +++ b/nirukta/timelines/utterance.py @@ -12,6 +12,7 @@ C_LABEL_ANIM_OUT, C_LABEL_ANIM_INDICATION, DOWN, + LEFT, ORIGIN, UP, WHITE, @@ -207,19 +208,21 @@ def construct(self): states[0].append(TypstText(set_font(sanskrit, SANSKRIT_FONT))) states[1].append(TypstText(set_font(translit, LATIN_FONT))) - states[2].append(TypstText(set_font(english, LATIN_FONT))) + states[2].append(TypstText(set_font(english, LATIN_FONT, wrap=True))) + + # Position everything relative to the final state to minimize movement + final_translit = states[1][len(states[1]) - 1] + final_translit.points.move_to(ORIGIN) for i in range(len(states[0])): # Start the transliteration in the center - states[1][i].points.move_to(ORIGIN) - - # Move sa and en above and below - states[0][i].points.next_to(states[1][i], UP) - states[2][i].points.next_to(states[1][i], DOWN) - - # TODO: find a way to align on edges without making the english text move every expansion change - # states[0][i].points.next_to( states[1][i], direction=UP * SCALE, aligned_edge=LEFT) - # states[2][i].points.next_to( states[1][i], direction=DOWN * SCALE, aligned_edge=LEFT) + states[0][i].points.next_to(final_translit, direction=UP, aligned_edge=LEFT) + states[1][i].points.next_to( + final_translit, direction=ORIGIN, aligned_edge=LEFT + ) + states[2][i].points.next_to( + final_translit, direction=DOWN, aligned_edge=LEFT + ) # Initial write on if i == 0: From 2e3ee3fb8b47106c81fd85917f38ea172a8860ad Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Fri, 29 May 2026 15:30:34 -0400 Subject: [PATCH 50/50] ignore initial `oM` in meter identification --- nirukta/models/presentation/sloka.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/nirukta/models/presentation/sloka.py b/nirukta/models/presentation/sloka.py index a0c57bc..08045bc 100644 --- a/nirukta/models/presentation/sloka.py +++ b/nirukta/models/presentation/sloka.py @@ -36,7 +36,10 @@ def slp1(self): if isinstance(token, str): slp1 += token else: - slp1 += token.slp1 + " " + if slp1 == "" and token.slp1 == "oM": + continue + else: + slp1 += token.slp1 + " " slp1 += "\n" return slp1