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/demo.py b/demo.py new file mode 100644 index 0000000..0e07714 --- /dev/null +++ b/demo.py @@ -0,0 +1,102 @@ +# 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), + scale=0.5, + ).show() + + 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) + border.show() + + self.forward(4) 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 { 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." 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/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/library/redux.sloka b/library/redux.sloka new file mode 100644 index 0000000..3db990c --- /dev/null +++ b/library/redux.sloka @@ -0,0 +1,14 @@ +=== sloka === + +--- line --- +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]=brahmavizRusivAkya->brahmavizRusivAkyAye +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." diff --git a/library/sUtrARi/SrIsarasvatIstotraM.sutra b/library/sUtrARi/SrIsarasvatIstotraM.sutra index e7187af..24bb63f 100644 --- a/library/sUtrARi/SrIsarasvatIstotraM.sutra +++ b/library/sUtrARi/SrIsarasvatIstotraM.sutra @@ -10,13 +10,13 @@ SrI[radiant]+gaReSAya[to][Ganesha]=SrIgaReSAya namaH[Reverence] . --- line --- yA[She who] -kunda[Jasmine flower]+indu[Moon]+tuzAra[frosty]+hAra[garland of pearls]+Davala[dazzling]=kundendutuzArahAraDavalA +kunda[Jasmine flower]+indu[Moon]+tuzAra[frosty]+hAra[garland of pearls]+Davala[dazzling]=kundendutuzArahAraDavala->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 +SuBra[shining]+vastra[clothing]+Avfta[covered]=SuBravastrAvfta->SuBravastrAvftA "She who is covered by shining clothing." --- line --- @@ -31,14 +31,12 @@ 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." --- line --- -sA[that] -mAM[me] -pAtu[May][protect] +sA[that] mAM[me] pAtu[May][protect] sarasvatI[Sarasvatī] BagavatI[divine] niSSeza[completely]+jAqya[ignorance]+apahA[removes]=niSSezajAqyApahA .. 1 .. @@ -48,30 +46,30 @@ niSSeza[completely]+jAqya[ignorance]+apahA[removes]=niSSezajAqyApahA .. 1 .. --- line --- dorBiH[arms]+yuktA[Endowed]=dorBiryuktA -catur[four]=caturBiH +catur[four]->caturBiH sPawika[crystal]+maRi[gems]+niBEH[resembling]+akzamAlAn[rosary]+daDAnA[holding]=sPawikamaRiniBErakzamAlAndaDAnA "Endowed with four arms, holding a rosary of beads resembling crystal gems," hastena[in][hand]+ekena[one]=hastenEkena -padma[lotus]=padmaM +padma[lotus]=>padmaM sitam[white]+api[also]+ca[and]=sitamapica -Sukam[parrot]=SukaM -pustakam[book]=pustakaM +Sukam[parrot]=>SukaM +pustakam[book]=>pustakaM ca[and]+apareRa[with another]=cApareRa . "in one hand is a white lotus, also holding a parrot and book with another." --- line --- -BAs[lustre]=BAsA -kunda[Jasmine flower]+indu[moon]+SaNKa[conch]+sPawita[crystal]+maRi[gem]+niBA[resembling]=kundenduSaNKasPawikamaRiniBA +BAs[lustre]->BAsA +kunda[Jasmine flower]+indu[moon]+SaNKa[conch]+sPawika[crystal]+maRi[gem]+niBA[resembling]=kundenduSaNKasPawikamaRiniBA BAsamAnA[Shining]+asamAnA[incomparable]=BAsamAnA'samAnA "Shining with an incomparable lustre, resembling Jasmine flower, moon, conch, crystal, and gem," --- line --- sA[she] me[my] -(vAc[speech]+devatA[Goddess]+iyam[this]=vAgdevateyam)=vAgdevateyaM +(vAc[speech]=>vAK)+devatA[Goddess]+iyam[this]=vAgdevateyam=>vAgdevateyaM nivasatu[may][reside] -vadan[in][mouth]=vadane +vadan[in][mouth]=>vadane sarvadA[always] su[very]+prasannA[pleased]=suprasannA .. 2 .. "may she, this Goddess of speech, be very pleased and always reside in my mouth." @@ -94,32 +92,38 @@ 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ī] sarasija[lotus]+kesara[filaments]+praBA[radiance]=sarasijakesarapraBA +"Sarasvatī, whose radiance is like lotus filaments," + 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," +"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] +kamala[lotus]+vilola[darting]+locanA[eyes]=kamalavilolalocanA +"full breasted, with darting lotus eyes -" + +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." +"may she be high-minded and gracious with her boons." === sloka === --- line --- sarasvati[O Sarasvatī] -(namas[reverence]+tuByam[to you]=namastuByam)=namastuByaM +namaH[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 +vidyA[learning]+AramBam[beginning]=vidyAramBam=>vidyAramBaM karizyAmi[I will undertake] "I will undertake the beginning of learning;" @@ -132,230 +136,251 @@ sadA[always] .. 5 .. --- line --- sarasvati[O Sarasvatī] -(namas[reverence]+tuByam[to you]=namastuByam)=namastuByaM +namaH[reverence]+tuByam[to you]=namastuByam=>namastuByaM "O Sarasvatī, reverence to you." sarva[all]+devi[O Goddess]=sarvadevi -namaH[reverence]=namo namaH[reverence] . +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 -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." +"O you of tranquil form, O bearer of the moon," + +sarva[all]+yoga[disciplines]=sarvayoga->sarvayoge +namaH[reverence]=>namo namaH[reverence] .. 6 .. +"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." +nitya[constant]+Ananda[in][bliss]=nityAnanda->nityAnande +nir[un]+ADAra[supported]=nirADAra->nirADAre +"O you in constant bliss, O unsupported one -" + +nizkala[undivided]->nizkalAyE namaH[reverence]=>namo namaH[reverence] . +"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 -namaH[reverence]=namo namaH[reverence] .. 7 .. -"O bearer of knowledge, O large eyed one, O embodier of pure knowledge, reverence, reverence." +"O bearer of knowledge, O large eyed one," + +SudDa[pure]+jYAna[knowledge]=SudDajYAna->SudDajYAne +namaH[reverence]=>namo namaH[reverence] .. 7 .. +"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] . +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 -namaH[reverence]=namo namaH[reverence] .. 8 .. -"O you who are Sabda Brahman, O four-handed one - reverence, reverence to her who is all accomplishment." +Sabda[Sabda]+brahma[Brahman]=Sabdabrahma->Sabdabrahmi +catur[four]+hasta[handed]=caturhasta->caturhaste +"O you who are Sabda Brahman, O four-handed one -" + +sarva[all]+sidDa[accomplishment]=sarvasidDa->sarvasidDyE +namaH[reverence]=>namo namaH[reverence] .. 8 .. +"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] . +mukta[pearls]+AlaNkfta[adorned]+sarva[every]+aNga[limb]=muktAlaNkftasarvANga->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 .. +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 -namaH[reverence]=namo namaH[reverence] . -"O you who consist of the mind, O embodier of great yoga, O Goddess of speech, reverence, reverence." +(manas[mind]->manaH=>mano)+mayi[consist]=manomayi +mahA[great]+yoga[yoga]=mahAyoga->mahAyoge +"O you who consist of the mind, O embodier of great yoga," + +vAk[speech]+ISvarI[Goddess]=vAgISvarI->vAgISvari +namaH[reverence]=>namo namaH[reverence] . +"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 .. +vARa[music]->vARyE +vara[boon]+da[giving]+hasta[hand]=varadahasta->varadahastAyE +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." === sloka === --- line --- -veda[knowledge]=vedyAyE -veda[knowledge]+rUpa[form]=vedarUpAyE -veda[Vedas]+antara[culmination]=vedAntAyE -namaH[Reverence]=namo namaH[reverence] . +veda[knowledge]->vedyAyE +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 -namaH[Reverence]=namo namaH[reverence] .. 11 .. +guRa[virtues]+doza[faults]+vivarjita[free from]=guRadozavivarjita->guRadozavivarjinyE +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 -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." +sarva[all]+jYAna[knowledge]=sarvajYAna->sarvajYAne +sadA[always]+Ananda[bliss]=sadAnanda->sadAnande +"O you who are all knowledge, O you who are always in bliss," + +sarva[all]+rUpa[form]=sarvarUpa->sarvarUpe +namaH[reverence]=>namo namaH[reverence] . +"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 .. +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] . +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 -namaH[Reverence]=namo namaH[reverence] .. 13 .. +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." === sloka === --- line --- -arDa[half]+candra[moon]+jawA[twisted hair]+DAra[wears]=arDacandrajawADAri -candra[moon]+bimba[reflection]=candrabimbe -namaH[reverence]=namo namaH[reverence] . -"O you who wears the half moon in twisted hair, O reflection of the moon - reverence, reverence." +arDa[half]+candra[moon]+jawA[twisted hair]+DAra[wears]=arDacandrajawADAra->arDacandrajawADAri +"O you who wears the half moon in twisted hair," + +candra[moon]+bimba[reflection]=candrabimba->candrabimbe +namaH[reverence]=>namo namaH[reverence] . +"O reflection of the moon - reverence, reverence." --- line --- -candra[moon]+aditya[sun]+jawA[twisted hair]+DAra[wear]=candrAdityajawADAri -candra[moon]+bimba[reflection]=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." +candra[moon]+aditya[sun]+jawA[twisted hair]+DAra[wear]=candrAdityajawADAra->candrAdityajawADAri +"O you who wear the sun and moon in twisted hair," + +candra[moon]+bimba[reflection]=candrabimba->candrabimbe +namaH[reverence]=>namo namaH[reverence] .. 14 .. +"O reflection of the moon - reverence, reverence." === sloka === --- line --- -aRu[atomic]+rUpa[form]=aRurUpe -mahA[large]+rUpa[form]=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." +aRu[atomic]+rUpa[form]=aRurUpa->aRurUpe +mahA[large]+rUpa[form]=mahArUpa->mahArUpe +"O you of atomic form, O you of large form," + +viSva[universal]+rUpa[form]=viSvarUpa->viSvarUpe +namaH[reverence]=>namo namaH[reverence] . +"O you of universal form - reverence, reverence." --- line --- -aRima[Aṇimā]+Adi[beginning]+azwa[eight-fold]+sidDi[powers]=aRimAdyazwasidDAyE -Ananda[bliss]=AnandAyE -namaH[Reverence]=namo namaH[reverence] .. 15 .. +aRima[Aṇimā]+Adi[beginning]+azwa[eight-fold]+sidDi[powers]=aRimAdyazwasidDi->aRimAdyazwasidDAyE +Ananda[bliss]->AnandAyE +namaH[Reverence]=>namo namaH[reverence] .. 15 .. "Reverence, reverence to the embodiment of bliss, to the eight-fold powers beginning with Aṇimā." === sloka === --- line --- -jYAna[knowledge]+vijYana[wisdom]+rUpa[form]=jYAnavijYAnarUpAyE -jYAna[knowledge]+mURtI[embodiment]=jYAnamUrte -namaH[reverence]=namo namaH[reverence] . +jYAna[knowledge]+vijYAna[wisdom]+rUpa[form]=jYAnavijYAnarUpa->jYAnavijYAnarUpAyE +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 -namaH[reverence]=namo namaH[reverence] .. 16 .. +nAnA[various]+SAstra[scriptures]+svarUpa[nature]=nAnASAstrasvarUpa->nAnASAstrasvarUpAyE +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 -namaH[reverence]=namo namaH[reverence] . -"O lotus-giver, O you of the lotus lineage and you in the form of a lotus - reverence, reverence." +"O lotus-giver, O you of the lotus lineage and" + +padma[lotus]+rUpa[form]=padmarUpa->padmarUpe +namaH[reverence]=>namo namaH[reverence] . +"you in the form of a lotus - reverence, reverence." --- line --- -paramezWyE[supreme Goddess] -parA[supreme]+mUrtI[embodiment]=parAmUrtyE -namas[reverence]+te[to you]=namaste +paramezWyE[supreme][Goddess] +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 -namaH[Reverence]=namo namaH[reverence] . -"Reverence, reverence to the great Goddess, to the great Kālī, to the great Lakṣmī" +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 -namaH[Reverence]=namo namaH[reverence] .. 18 .. -"Reverence, reverence to she who is known as Brahman, Viṣṇu, Śiva, to the divine woman." +brahma[brahma]+vizRu[viṣṇu]+siva[śiva]+akya[known as]=brahmavizRusivAkya->brahmavizRusivAkyAye +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." === sloka === --- line --- kamala[lotus]+Akara[source]+puzpA[flower]=kamalAkarapuzpA ca[and] -kAma[desire]+rUpa[the form]=kAmarUpe -namaH[reverence]=namo namaH[reverence] . +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 -namaH[reverence]=namo namaH[reverence] .. 19 .. +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." === sloka === --- line --- -sAyam[in the evening]=sAyaM +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 . +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 +paWatAm[those who recite]=>paWatAM SfRvatAm[those who listen]+api[Also]=SfRvatAmapi .. 20 .. "Also, fear of thieves and tigers does not exist for those who recite this or those who listen to this." @@ -363,17 +388,19 @@ SfRvatAm[those who listen]+api[Also]=SfRvatAmapi .. 20 .. --- line --- itTaM[Thus] -sarasvatI[Sarasvatī]+stotra[stotra]+agastya[Agastya]+muni[sage]+vAcaka[the speaking of]=sarasvatIstotramagastyamunivAcakam . +sarasvatI[Sarasvatī]+stotram[stotra]+agastya[Agastya]+muni[sage]+vAcaka[the speaking of]=sarasvatIstotramagastyamunivAcaka->sarasvatIstotramagastyamunivAcakam . "Thus, the speaking of sage Agastya's Sarasvatī stotram" --- line --- -(sarva[all]+sidDi[skills]+kara[production]=sarvasidDikaram)=sarvasidDikaraM +sarva[all]+sidDi[skills]+kara[production]=sarvasidDikara->sarvasidDikaram=>sarvasidDikaraM nFRAM[leads to] -sarva[all]+pApa[sins]+praRASana[destruction]=sarvapApapraRASanam .. 21 .. +sarva[all]+pApa[sins]+praRASana[destruction]=sarvapApapraRASana->sarvapApapraRASanam .. 21 .. "leads to the production of all skills and the destruction of all sins." === 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/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/library/sUtrARi/vakratuRqa_mahAkAya.sutra b/library/sUtrARi/vakratuRqa_mahAkAya.sutra new file mode 100644 index 0000000..d8a8676 --- /dev/null +++ b/library/sUtrARi/vakratuRqa_mahAkAya.sutra @@ -0,0 +1,13 @@ +=== unknown === + +=== 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 . 5 . +"O god - make the way free of obstacles for me," +"in all endeavors and all times." 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/main.py b/main.py index 389bf32..f5b25c6 100644 --- a/main.py +++ b/main.py @@ -16,19 +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 @@ -38,4 +41,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/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/__init__.py b/nirukta/models/__init__.py index 1d22325..72d5f7d 100644 --- a/nirukta/models/__init__.py +++ b/nirukta/models/__init__.py @@ -1,4 +1,4 @@ -from nirukta.models.enums import Language, Animation +from nirukta.models.enums import Language, System, Animation from nirukta.models.gloss import EnglishGloss, EtymGloss, Gloss from nirukta.models.tokens import ( SimpleToken, diff --git a/nirukta/models/enums.py b/nirukta/models/enums.py index 70b03d3..1d2723b 100644 --- a/nirukta/models/enums.py +++ b/nirukta/models/enums.py @@ -1,13 +1,17 @@ from typing import Optional from enum import Enum +class System(Enum): + IAST = "IAST" + WX = "WX" + SLP1 = "SLP1" + DEVANAGARI = "DEVANAGARI" class Language(Enum): ENGLISH = "english" SANSKRIT = "sanskrit" TRANSLIT = "translit" - class Animation(Enum): # Swara removal SWARAS = "Swara" 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 diff --git a/nirukta/models/presentation/akshara.py b/nirukta/models/presentation/akshara.py new file mode 100644 index 0000000..f30a487 --- /dev/null +++ b/nirukta/models/presentation/akshara.py @@ -0,0 +1,25 @@ +from attr import dataclass +from janim.imports import BLUE_E, RED_E +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) + + 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 154d604..08045bc 100644 --- a/nirukta/models/presentation/sloka.py +++ b/nirukta/models/presentation/sloka.py @@ -1,8 +1,10 @@ from dataclasses import dataclass +from aksharamukha import transliterate from typing import List, Optional from nirukta.constants import DIGITS_RE +from nirukta.models.presentation.akshara import Akshara, identify from nirukta.models.presentation.line import Line @@ -25,3 +27,72 @@ 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: + if slp1 == "" and token.slp1 == "oM": + continue + else: + slp1 += token.slp1 + " " + + slp1 += "\n" + return slp1 + + 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: 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: List[Akshara] = [] + for si in range(len(line_sounds)): + aksharas.append( + Akshara(text=line_sounds[si], weight=weight_lines[li][si]) + ) + padas.append(aksharas) + + # print(verse.meter_label) + # print(verse.morae_per_line) + # print(padas) + + label = transliterate.process("IAST", "SLP1", verse.meter_label) + assert label is not None + + # if "(" in label: + 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/parsing/grammars.py b/nirukta/parsing/grammars.py index 19abc20..8ca9306 100644 --- a/nirukta/parsing/grammars.py +++ b/nirukta/parsing/grammars.py @@ -11,15 +11,21 @@ token_seq = token (ws token)* - token = compound_token / simple_token / punct + token = text_token / punct + + text_token = (equation_part / simple_token) inflect_part* external_part* + + equation_part = comp_part plus_part+ "=" slp1 + + # Inflection transforms + inflect_part = "->" slp1 + + # External sandhi transforms + external_part = "=>" slp1 - # 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? plus_part = "+" comp_part comp_part = paren_compound / simple_token - paren_compound = "(" compound_token ")" + paren_compound = "(" text_token ")" simple_token = slp1 gloss* gloss = trans_gloss / etym_gloss @@ -32,7 +38,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*" @@ -45,8 +51,8 @@ inline_sloka = ws "=== sloka ===" ws line+ ws external_sloka = ws "=== sloka ===" ws file ws file = "file:" file_content - file_part = ~r"[a-zA-Z0-9._]+" - file_content = file_part ("/" file_part)+ + file_part = ~r"[a-zA-Z0-9._]+" + file_content = file_part ("/" file_part)+ """ + "\n" + SLOKA_GRAMMAR_STR diff --git a/nirukta/parsing/visitors/sloka.py b/nirukta/parsing/visitors/sloka.py index fda3463..7999aa7 100644 --- a/nirukta/parsing/visitors/sloka.py +++ b/nirukta/parsing/visitors/sloka.py @@ -1,12 +1,17 @@ import logging import os import traceback -from typing import Optional +from typing import List, Union +from nirukta.render import transliterate +from nirukta.strings import unswara +import sandhi as sandhi_module +from janim.imports import log from nirukta.inflection import Case, SanskritInflection from nirukta.models import ( CompoundToken, EnglishGloss, - EtymGloss, + Language, + System, Line, SimpleToken, Sloka, @@ -14,8 +19,53 @@ ) from nirukta.models import SlokaFile from nirukta.parsing.grammars import SLOKA_GRAMMAR +from parsimonious.exceptions import ParseError from parsimonious.nodes import NodeVisitor +S = sandhi_module.Sandhi() + + + +def validate_equation(parts: List[Union[CompoundToken, SimpleToken]], result: str): + if len(parts) > 1: + built = "" + # log.info(f"parts: {parts}") + for i in range(len(parts)): + # log.info(f"part: {parts[i]}") + A = built + B = transliterate(System.SLP1, System.WX, unswara(parts[i].slp1)) + log.debug(f"adding: \'{transliterate(System.WX,System.IAST, A)}\' + \'{transliterate(System.WX,System.IAST, B)}\'") + + results = S.sandhi(A, B, input_scheme="wx") + valid_forms = {r[0] for r in results} + 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: + built = compact_results[0] + + + built = transliterate(System.WX, System.IAST, built) + final_result = transliterate(System.SLP1, System.IAST, unswara(result)) + final_result = final_result.replace("\'","") + + undone_parts = list( + map(lambda x: transliterate(System.SLP1, System.IAST, x.slp1), 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}") + else: + log.warning(f"no need to validate parts of n<2 {parts}") class SlokaVisitor(NodeVisitor): file: str @@ -33,11 +83,28 @@ def __init__(self, file: str): self.file = file self.directory = os.path.dirname(self.file) + def _print_parse_error(self, e: ParseError) -> None: + lines = self.source.splitlines() + before = self.source[:e.pos] + lineno = before.count("\n") + col = e.pos - before.rfind("\n") - 1 + ctx_start = max(0, lineno - 2) + ctx_end = min(len(lines), lineno + 3) + print(f"\nParse error at line {lineno + 1}, col {col + 1}: {e}\n") + for i, line in enumerate(lines[ctx_start:ctx_end], start=ctx_start + 1): + print(f" {i:4d} | {line}") + if i == lineno + 1: + print(f" | {' ' * col}^") + print() + def parse(self) -> SlokaFile: - tree = SLOKA_GRAMMAR.parse(self.source) + try: + tree = SLOKA_GRAMMAR.parse(self.source) + except ParseError as e: + self._print_parse_error(e) + raise return self.visit(tree) - # -- top level ---------------------------------------------------------- def visit_sloka(self, _, visited_children): @@ -81,20 +148,40 @@ 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 - ) + def visit_text_token(self, _, visited_children): + initial, inflect_parts, external_parts = visited_children - parts = [first_part] + list(plus_parts) + # Stat construction + result = initial - return CompoundToken( - parts=parts, - slp1=surface, - etym_gloss=etym_gloss, - ) + # 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)] + + for slp1 in i_parts: + result = CompoundToken(parts=[result], slp1=slp1) + + e_parts = external_parts if isinstance(external_parts, list) else [] + e_parts = [p for p in e_parts if isinstance(p, str)] + for slp1 in e_parts: + result = CompoundToken(parts=[result], slp1=slp1) + + return result + + def visit_equation_part(self, _, visited_children): + first_part, plus_parts, _, slp1 = visited_children + + parts = list([first_part]) + list(plus_parts) + validate_equation(parts, slp1) + return CompoundToken(parts, slp1) + + def visit_inflect_part(self, _, visited_children): + _, slp1 = visited_children + return slp1 + + def visit_external_part(self, _, visited_children): + _, slp1 = visited_children + return slp1 def visit_plus_part(self, _, visited_children): _, part = visited_children @@ -105,7 +192,12 @@ def visit_comp_part(self, _, visited_children): def visit_paren_compound(self, _, visited_children): _, compound, _ = visited_children - return compound + + if isinstance(compound, list): + assert len(compound) == 1 + return compound[0] + else: + return compound # -- simple tokens & glosses -------------------------------------------- @@ -124,7 +216,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/nirukta/parsing/visitors/sutra.py b/nirukta/parsing/visitors/sutra.py index d905ca1..69bfa82 100644 --- a/nirukta/parsing/visitors/sutra.py +++ b/nirukta/parsing/visitors/sutra.py @@ -3,11 +3,16 @@ from nirukta.models import Sloka, SutraFile from nirukta.parsing.grammars import SUTRA_GRAMMAR from nirukta.parsing.visitors.sloka import SlokaVisitor +from parsimonious.exceptions import ParseError class SutraVisitor(SlokaVisitor): def parse(self) -> SutraFile: - tree = SUTRA_GRAMMAR.parse(self.source) + try: + tree = SUTRA_GRAMMAR.parse(self.source) + except ParseError as e: + self._print_parse_error(e) + raise return self.visit(tree) def visit_sutra(self, _, visited_children): 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() diff --git a/nirukta/render.py b/nirukta/render.py index 158019f..d026611 100644 --- a/nirukta/render.py +++ b/nirukta/render.py @@ -1,42 +1,29 @@ from janim.imports import ( C_LABEL_ANIM_DEFAULT, C_LABEL_ANIM_OUT, - GREEN, - LEFT, - MED_SMALL_BUFF, - RED, - UL, Aligned, Cmpt_Rgbas, DataUpdater, - ItemUpdater, RateFunc, - Rect, SupportsAnim, AnimGroup, Succession, - Indicate, DOWN, UP, FadeOut, Group, GrowFromEdge, ShrinkToEdge, - SurroundingRect, - Text, - TypstText, - VItem, ValueTracker, double_smooth, linear, rush_into, smooth, - there_and_back, ) -from nirukta.constants import INACTIVE, INTRO_FONT, LATIN_FONT, SCALE, TYPST_CMD_RE -from nirukta.models import Language, Sloka +from nirukta.constants import INACTIVE, TYPST_CMD_RE +from nirukta.models import Language, System from janim.imports import WHITE, C_LABEL_ANIM_ABSTRACT -from aksharamukha import transliterate +from aksharamukha.transliterate import process from dataclasses import dataclass, field from nirukta.models import Animation @@ -149,7 +136,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: @@ -183,83 +170,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_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: @@ -283,8 +193,14 @@ def T(s): return f"#box[{inner}]" -def set_font(text: str, font: str): - return f'#set text(font: "{font}", stroke: none)\n#set page(width: {266 * SCALE}pt)\n{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 * ratio}pt)\n" + + result += f"{text}" + return result def text_box(text: str, color: str, stroke_mode: bool = False): @@ -312,22 +228,39 @@ def typst_code_safe(text: str, language: Language, color: str = WHITE) -> str: result += typst_code(part, language, color) return result +def transliterate(src: System, dst: System, text: str): + if text == "": + return text + + result = process(src.value, dst.value, text) + if not result: + raise ValueError(f'Cannot transform "{text}" from {src} to {dst}') + + return result def transform_text(text: str, language: Language): match language: case Language.ENGLISH: return text case Language.TRANSLIT: - iast = transliterate.process("SLP1", "IAST", text) + iast = process("SLP1", "IAST", text) if not iast: raise ValueError(f'Cannot represent "{text}" in IAST') return iast case Language.SANSKRIT: - deva = transliterate.process("SLP1", "DEVANAGARI", text) + deva = process("SLP1", "DEVANAGARI", text) if not deva: raise ValueError(f'Cannot represent "{text}" in devanagari') return deva +def untransform_text(text: str): + if text == "": + return "" + iast = 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 @@ -336,8 +269,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 new file mode 100644 index 0000000..c8c8505 --- /dev/null +++ b/nirukta/sloka.py @@ -0,0 +1,158 @@ +from attr import dataclass +from janim.imports import ( + LEFT, + UP, + Group, + TypstText, + Config, +) +from nirukta.constants import SANSKRIT_FONT, LATIN_FONT +from nirukta.models import Language, Sloka + +from nirukta.render import set_font, transform_text, typst_code +from typing import List + +from nirukta.typst import add_linebreaks, arrange_vertical, box_cell, arrange_horizontal + + +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) + grid = add_linebreaks(rows) + + return TypstText( + set_font(grid, LATIN_FONT), + ) + + +def sloka_group_reformed(sloka: Sloka, devanagari: bool) -> 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 + " " + + # rows.append(f"[{sanskritcode}]") + rows.append(sanskritcode) + + # grid = arrange_vertical(rows, gutter=0.6) + grid = add_linebreaks(rows) + + return TypstText( + set_font(grid, font), + ) + + +@dataclass +class Keyed: + text: TypstText + keys: Group + + +def sloka_group_chandas( + sloka: Sloka, + blank: bool = False, + matras: bool = False, + devanagari: bool = True, +) -> Keyed: + if devanagari: + lang = Language.SANSKRIT + font = SANSKRIT_FONT + else: + lang = Language.TRANSLIT + font = LATIN_FONT + + all_cells = [] + cell_idx = 0 + + (meter_label, padas) = sloka.meter() + + for pada in padas: + for akshara in pada: + deva = transform_text(akshara.text, lang) + fill = None if blank else akshara.rgb_color() + all_cells.append( + box_cell( + content=deva, + wide=matras and akshara.is_long(), + idx=cell_idx, + fill=fill, + ) + ) + cell_idx += 1 + + rows = [] + row_labels = [] + + 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 = arrange_vertical(rows) + + # Actual ratio of text to use + columns = len(padas[0]) + ratio = min(Config.get.frame_width / columns, 1.0) + + grid = TypstText(set_font(grid_code, font, ratio)) + + if blank: + return Keyed(text=grid, keys=Group()) + + 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], ratio: float +) -> Group: + # Position title and labels relative to the centered grid + meter_deva = transform_text(meter_label, Language.SANSKRIT) + title = TypstText( + 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)] + 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(label_text, SANSKRIT_FONT, ratio=ratio), + ) + label.points.next_to(texttttt.get_label(c_label), LEFT) + labelz.append(label) + + return Group(title, *labelz) diff --git a/nirukta/timelines/english.py b/nirukta/timelines/english.py new file mode 100644 index 0000000..7633562 --- /dev/null +++ b/nirukta/timelines/english.py @@ -0,0 +1,48 @@ +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 + + +@dataclass +class EnglishTimeline(Timeline): + sloka: Sloka + + def __init__(self, sloka: Sloka): + super().__init__() + self.sloka = sloka + + def construct(self): + rows = [] + + for line in self.sloka.lines: + for vAkya in line.vAkyAni: + rows.append(typst_code(vAkya.english, Language.ENGLISH)) + + # 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(list(map(lambda code: f"[{code}]", rows)), gutter=0.6) + + group = TypstText( + set_font(grid, LATIN_FONT, wrap=True), + ) + + 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 f84955a..2481e90 100644 --- a/nirukta/timelines/explain_sloka.py +++ b/nirukta/timelines/explain_sloka.py @@ -1,17 +1,17 @@ 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 -from janim.logger import log -from nirukta.models import Line, Sloka -from nirukta.render import Awaken, Sleep, sloka_group, sloka_thumbnail -from nirukta.timelines import ( - LenientTransformMatchingDiff, - UtteranceTimeline, - build_utterance_cached, +from janim.imports import ( + YELLOW, + Timeline, + Wait, + TransformableFrameClip, ) -from nirukta.timelines.line import LineTimeline +from janim.logger import log +from nirukta.models import Sloka +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. _built_cache: dict[str, Any] = {} @@ -40,23 +40,32 @@ 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]))) + # 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, + offset=(-0.25, 0.25), + scale=0.5, + ).show() + + 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(Sleep(thumbnail[0])) - - selection = thumbnail[0][li].get_label(f"line_{li}_utterance_{vi}") - self.play(Awaken(selection)) + self.play(Wait(0.33)) - vt = build_utterance_cached(vAkya).to_item().show() - self.forward_to(vt.end) + self.play(Wait(0.33)) + # vt = build_utterance_cached(vAkya).to_item().show() + vt = UtteranceTimeline(vAkya).build().to_item().show() + self.forward(vt.duration) - self.play(Sleep(thumbnail[0])) - self.play(FadeOut(thumbnail)) + self.forward_to(thumb.end) diff --git a/nirukta/timelines/introduce_quad.py b/nirukta/timelines/introduce_quad.py new file mode 100644 index 0000000..a56ed80 --- /dev/null +++ b/nirukta/timelines/introduce_quad.py @@ -0,0 +1,50 @@ +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 + first: bool + last: bool + + 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: + 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), + ], + first=self.first, + last=self.last, + scale=0.5, + ) + .build() + .to_item() + .show() + ) + self.forward(quadrants.duration) diff --git a/nirukta/timelines/introduce_sloka.py b/nirukta/timelines/introduce_sloka.py index e0c5965..ce5e06e 100644 --- a/nirukta/timelines/introduce_sloka.py +++ b/nirukta/timelines/introduce_sloka.py @@ -1,8 +1,30 @@ 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 ( + FadeIn, + DOWN, + YELLOW, + FadeOut, + Group, + Timeline, + TypstText, + Wait, + Write, + Aligned, +) + +from nirukta.constants import SANSKRIT_FONT, SCALE from nirukta.models import Language, Sloka -from nirukta.render import sloka_group, set_font, typst_code -from nirukta.constants import INTRO_FONT, SCALE +from nirukta.render import ( + set_font, + typst_code, +) +from nirukta.sloka import ( + sloka_group_chandas, + sloka_group_reformed, +) +from nirukta.timelines.transform import LenientTransformMatchingDiff class IntroduceSloka(Timeline): @@ -19,25 +41,57 @@ def gui_color(self) -> str: return YELLOW def construct(self): - sloka_g = sloka_group(self.sloka) + sloka_g = sloka_group_reformed(self.sloka, devanagari=True) + + # 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) + # 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, + # ) + # ) - for line in sloka_g: - self.play(Write(line, duration=4.0)) + 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, + set_font(typst_code(self.citation, Language.SANSKRIT), SANSKRIT_FONT), ) print(citation_text.text) citation_text.points.next_to(sloka_g, DOWN) for animation in [ - Wait(2.0), Write(citation_text, duration=1.0), Wait(1.0), FadeOut(Group(sloka_g, citation_text)), ]: self.play(animation) else: - self.play(Wait(1.0)) self.play(FadeOut(sloka_g)) diff --git a/nirukta/timelines/quadrants.py b/nirukta/timelines/quadrants.py new file mode 100644 index 0000000..ef3e42e --- /dev/null +++ b/nirukta/timelines/quadrants.py @@ -0,0 +1,86 @@ +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] + scale: float + first: bool + last: bool + + 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.") + + super().__init__() + self.timelines = timelines + self.scale = scale + self.first = first + self.last = last + + @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) + + 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)] + durations = [] + + for idx, timeline in enumerate(self.timelines): + built_timeline = timeline.build().to_item().show() + TransformableFrameClip( + built_timeline, offset=offsets[idx], scale=self.scale + ).show() + durations.append(built_timeline.duration) + + # Wait + self.forward(max(durations)) + + if self.last: + self.play(FadeOut(borders)) diff --git a/nirukta/timelines/recitation.py b/nirukta/timelines/recitation.py new file mode 100644 index 0000000..eaf49ae --- /dev/null +++ b/nirukta/timelines/recitation.py @@ -0,0 +1,70 @@ +from dataclasses import dataclass +import sys + +from janim.imports import ( + Aligned, + Config, + FadeIn, + FadeOut, + Group, + Succession, + Timeline, + Transform, + Wait, +) +from nirukta.models import Sloka +from nirukta.sloka import sloka_group_chandas, sloka_group_reformed +from nirukta.timelines import LenientTransformMatchingDiff + + +@dataclass +class RecitationTimeline(Timeline): + sloka: Sloka + devanagari: bool + chandas: bool + + def __init__(self, sloka: Sloka, devanagari: bool, chandas: bool): + super().__init__() + self.sloka = sloka + self.devanagari = devanagari + self.chandas = chandas + + def construct(self): + 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( + FadeIn(group), + Wait(1.0), + ) + ) + self.play(Succession(Wait(2.0), FadeOut(group, duration=0.5))) + + """ if self.chandas: + blank = sloka_group_chandas(self.sloka, blank=True, matras=False) + chandas = sloka_group_chandas(self.sloka, blank=False, matras=False) + + self.play( + Succession( + LenientTransformMatchingDiff(group, blank.text, duration=0.5), + Aligned( + Transform(blank.text, chandas.text), + FadeIn(chandas.keys), + duration=0.5, + ), + Wait(1.0), + FadeOut( + Group( + chandas.keys, + chandas.text, + ), + duration=0.5, + ), + ) + ) + else: + self.play(Succession(Wait(2.0), FadeOut(group, duration=0.5))) """ diff --git a/nirukta/timelines/sloka_file.py b/nirukta/timelines/sloka_file.py index c11b4b1..553b904 100644 --- a/nirukta/timelines/sloka_file.py +++ b/nirukta/timelines/sloka_file.py @@ -1,8 +1,8 @@ -from janim.imports import ORANGE, FadeOut, Timeline, Wait, Write +from janim.imports import ORANGE, Timeline from nirukta.models import Sloka, SlokaFile -from nirukta.render 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 +from nirukta.timelines.introduce_quad import IntroduceQuadTimeline class SlokaFileTimeline(Timeline): @@ -23,16 +23,18 @@ 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) - - # 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)) + introduction = ( + IntroduceSloka(self.sloka, self.citation).build().to_item().show() + ) + self.forward(introduction.duration) + + quadrants = ( + IntroduceQuadTimeline(self.sloka, first=True, last=True) + .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 1541070..538012e 100644 --- a/nirukta/timelines/sutra_file.py +++ b/nirukta/timelines/sutra_file.py @@ -1,41 +1,24 @@ from dataclasses import dataclass -from typing import List, Optional +from typing import List from janim.imports import ( - LEFT, - MED_SMALL_BUFF, ORANGE, ORIGIN, - UL, - UP, - WHITE, - Aligned, - FadeIn, FadeOut, - Group, - Rect, - SurroundingRect, - Text, + Succession, Timeline, - Transform, TypstText, Wait, Write, ) -from nirukta.constants import INACTIVE, INTRO_FONT, SCALE +from nirukta.constants import SANSKRIT_FONT from nirukta.models import Language, Sloka, SutraFile -from nirukta.timelines import LenientTransformMatchingDiff, UtteranceTimeline from nirukta.render import ( - Awaken, - Sleep, - scale_with_stroke, set_font, - sloka_group_english, - sloka_thumbnail, typst_code, - sloka_group, ) -from nirukta.timelines.explain_sloka import ExplainSloka, build_explain_sloka_cached +from nirukta.timelines import ExplainSloka +from nirukta.timelines.introduce_quad import IntroduceQuadTimeline @dataclass @@ -57,42 +40,32 @@ def gui_color(self) -> str: return ORANGE def construct(self): - citation = TypstText( - set_font(typst_code(self.citation, Language.SANSKRIT), INTRO_FONT), - scale=SCALE, - ) - citation.points.move_to(ORIGIN) + if self.citation != "unknown": + citation = TypstText( + set_font(typst_code(self.citation, Language.SANSKRIT), SANSKRIT_FONT), + ) + citation.points.move_to(ORIGIN) - # Introduce the text by its title - for animation in [ - Write(citation), - Wait(1.5), - FadeOut(citation), - ]: - self.play(animation) + # Introduce the text by its title + self.play( + Succession( + Write(citation), + Wait(1.5), + FadeOut(citation), + ) + ) - 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) + 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) - # 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)) + 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 new file mode 100644 index 0000000..97eefcb --- /dev/null +++ b/nirukta/timelines/thumbnail.py @@ -0,0 +1,97 @@ +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)) + + # Build but do not show; so that we can match durations + # vt = build_utterance_cached(vAkya).to_item() + vt = UtteranceTimeline(vAkya).build().to_item() + self.forward(vt.duration) + + self.play(Sleep(sloka_text)) + self.play(FadeOut(group)) diff --git a/nirukta/timelines/utterance.py b/nirukta/timelines/utterance.py index 300aaad..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, @@ -29,7 +30,6 @@ LATIN_FONT, MISSING_CHUNK_RE, SANSKRIT_FONT, - SCALE, TYPST_CMD_RE, ALPHA_RE, WHITESPACE_RE, @@ -206,17 +206,23 @@ 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, 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 * SCALE) - states[2][i].points.next_to(states[1][i], DOWN * SCALE) + 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: diff --git a/nirukta/typst.py b/nirukta/typst.py new file mode 100644 index 0000000..da6d996 --- /dev/null +++ b/nirukta/typst.py @@ -0,0 +1,46 @@ +from typing import List, Optional + + +height = 1.5 +gutter = 0.2 + + +def box_cell( + content: str, + wide: bool, + idx: Optional[int] = None, + fill: Optional[str] = None, +) -> str: + # Fallback to blank + 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: {height}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)})" + ) + + +def add_linebreaks(lines: List[str]) -> str: + return f"{' #linebreak() '.join(lines)}" diff --git a/pyproject.toml b/pyproject.toml index 28b23c2..ab4207f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,8 +7,10 @@ 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", + "sandhi>=1.0.0", + "skrutable>=2.6.3", ] [build-system] 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 6510ed3..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" @@ -246,7 +280,7 @@ wheels = [ [[package]] name = "janim" -version = "4.1.0" +version = "4.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, @@ -265,9 +299,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] @@ -450,14 +484,18 @@ dependencies = [ { name = "dill" }, { name = "janim", extra = ["gui"] }, { name = "parsimonious" }, + { name = "sandhi" }, + { name = "skrutable" }, ] [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 = "sandhi", specifier = ">=1.0.0" }, + { name = "skrutable", specifier = ">=2.6.3" }, ] [[package]] @@ -844,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" @@ -871,6 +939,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" @@ -905,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" @@ -917,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" diff --git a/validate.py b/validate.py new file mode 100644 index 0000000..dc7c98a --- /dev/null +++ b/validate.py @@ -0,0 +1,18 @@ +from nirukta.util import ( + SlokaVisitor, + SutraVisitor, + choose_nirukta_file, + is_nirukta_file, +) + +chosen = choose_nirukta_file() +assert is_nirukta_file(chosen), "Invalid file" + +while True: + if ".sutra" in chosen: + SutraVisitor(chosen).parse() + else: + SlokaVisitor(chosen).parse() + c = input("validate again? y/n") + if c == "n": + break