scriptconv 0.0.1a6__tar.gz → 0.0.2a2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. {scriptconv-0.0.1a6 → scriptconv-0.0.2a2}/PKG-INFO +17 -4
  2. {scriptconv-0.0.1a6 → scriptconv-0.0.2a2}/README.md +16 -3
  3. {scriptconv-0.0.1a6 → scriptconv-0.0.2a2}/scriptconv/__init__.py +8 -0
  4. {scriptconv-0.0.1a6 → scriptconv-0.0.2a2}/scriptconv/notation.py +258 -0
  5. {scriptconv-0.0.1a6 → scriptconv-0.0.2a2}/scriptconv/version.py +2 -2
  6. {scriptconv-0.0.1a6 → scriptconv-0.0.2a2}/scriptconv.egg-info/PKG-INFO +17 -4
  7. {scriptconv-0.0.1a6 → scriptconv-0.0.2a2}/tests/test_examples.py +12 -0
  8. {scriptconv-0.0.1a6 → scriptconv-0.0.2a2}/tests/test_notation.py +92 -1
  9. {scriptconv-0.0.1a6 → scriptconv-0.0.2a2}/LICENSE +0 -0
  10. {scriptconv-0.0.1a6 → scriptconv-0.0.2a2}/pyproject.toml +0 -0
  11. {scriptconv-0.0.1a6 → scriptconv-0.0.2a2}/requirements.txt +0 -0
  12. {scriptconv-0.0.1a6 → scriptconv-0.0.2a2}/scriptconv/__main__.py +0 -0
  13. {scriptconv-0.0.1a6 → scriptconv-0.0.2a2}/scriptconv/py.typed +0 -0
  14. {scriptconv-0.0.1a6 → scriptconv-0.0.2a2}/scriptconv/scripts.py +0 -0
  15. {scriptconv-0.0.1a6 → scriptconv-0.0.2a2}/scriptconv/translit.py +0 -0
  16. {scriptconv-0.0.1a6 → scriptconv-0.0.2a2}/scriptconv.egg-info/SOURCES.txt +0 -0
  17. {scriptconv-0.0.1a6 → scriptconv-0.0.2a2}/scriptconv.egg-info/dependency_links.txt +0 -0
  18. {scriptconv-0.0.1a6 → scriptconv-0.0.2a2}/scriptconv.egg-info/requires.txt +0 -0
  19. {scriptconv-0.0.1a6 → scriptconv-0.0.2a2}/scriptconv.egg-info/top_level.txt +0 -0
  20. {scriptconv-0.0.1a6 → scriptconv-0.0.2a2}/setup.cfg +0 -0
  21. {scriptconv-0.0.1a6 → scriptconv-0.0.2a2}/tests/test_cli.py +0 -0
  22. {scriptconv-0.0.1a6 → scriptconv-0.0.2a2}/tests/test_scripts.py +0 -0
  23. {scriptconv-0.0.1a6 → scriptconv-0.0.2a2}/tests/test_translit.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scriptconv
3
- Version: 0.0.1a6
3
+ Version: 0.0.2a2
4
4
  Summary: Shared script-conversion core — ISO-15924 detection, IPA↔ARPA/X-SAMPA/Lexique, Buckwalter, Hangul→jamo
5
5
  License: Apache-2.0
6
6
  Project-URL: Homepage, https://github.com/TigreGotico/scriptconv
@@ -33,7 +33,7 @@ Dynamic: license-file
33
33
 
34
34
  **scriptconv** is a zero-dependency Python library for written-script operations:
35
35
  ISO-15924 script identification and metadata, character-range detection, language-to-script
36
- mapping, phoneme-notation transcoding (IPA ↔ ARPABET X-SAMPA, IPA Lexique,
36
+ mapping, phoneme-notation transcoding (IPA ↔ ARPABET / X-SAMPA / Lexique / Kirshenbaum / Cotovía / RFE,
37
37
  Buckwalter ↔ Arabic script), and orthographic decomposition of Hangul syllable blocks into
38
38
  jamo letters. Every conversion is a pure data table or arithmetic operation; no linguistic
39
39
  rules, no external files, no runtime dependencies beyond the Python standard library.
@@ -75,7 +75,8 @@ from scriptconv import (
75
75
  xsampa_to_ipa, ipa_to_xsampa,
76
76
  buckwalter_to_arabic, arabic_to_buckwalter,
77
77
  lexique_to_ipa, ipa_to_lexique,
78
- kirshenbaum_to_ipa, ipa_to_kirshenbaum, looks_like_ipa,
78
+ kirshenbaum_to_ipa, ipa_to_kirshenbaum, cotovia_to_ipa, ipa_to_cotovia,
79
+ rfe_to_ipa, ipa_to_rfe, looks_like_ipa,
79
80
  decompose_hangul, hira_to_kana, kana_to_hira,
80
81
  convert, can_convert, convert_batch, Notation, NOTATION_INFO,
81
82
  )
@@ -107,6 +108,12 @@ ipa_to_lexique("vɛ̃") # "v5" (vin)
107
108
  kirshenbaum_to_ipa("S") # "ʃ" (espeak-ng ASCII-IPA)
108
109
  ipa_to_kirshenbaum("ŋ") # "N"
109
110
 
111
+ cotovia_to_ipa("karro") # "karo" (Galician Cotovía TTS notation)
112
+ ipa_to_cotovia("ʎ") # "L"
113
+
114
+ rfe_to_ipa("kaša") # "kaʃa" (Spanish/Romance RFE alphabet)
115
+ ipa_to_rfe("ɲ") # "ñ"
116
+
110
117
  looks_like_ipa("pʰɑtʃ") # True (heuristic: has IPA-distinctive symbols)
111
118
  looks_like_ipa("hello") # False
112
119
 
@@ -139,7 +146,7 @@ python -m scriptconv lang ko
139
146
  | Module | Contents |
140
147
  |--------|----------|
141
148
  | `scriptconv.scripts` | `Script` dataclass (with `script_type`), `SCRIPT_REGISTRY` (34 scripts), `detect_script`, `char_script`, `script_distribution`, `script_runs`, `base_direction`, `lang_to_script`, `script_to_langs`, `normalize_script_tag` |
142
- | `scriptconv.notation` | `Notation` enum, `NotationInfo`/`NOTATION_INFO` fidelity registry, `convert` facade, `can_convert` predicate, `convert_batch` generator, pair-wise converters (ARPABET ↔ IPA, X-SAMPA ↔ IPA, Buckwalter ↔ Arabic, Lexique ↔ IPA, Kirshenbaum ↔ IPA), `looks_like_ipa` detector |
149
+ | `scriptconv.notation` | `Notation` enum, `NotationInfo`/`NOTATION_INFO` fidelity registry, `convert` facade, `can_convert` predicate, `convert_batch` generator, pair-wise converters (ARPABET ↔ IPA, X-SAMPA ↔ IPA, Buckwalter ↔ Arabic, Lexique ↔ IPA, Kirshenbaum ↔ IPA, Cotovía ↔ IPA, RFE ↔ IPA), `looks_like_ipa` detector |
143
150
  | `scriptconv.translit` | `decompose_hangul` (Hangul blocks → jamo, compatibility or conjoining), `hira_to_kana`/`kana_to_hira` — all orthographic only |
144
151
 
145
152
  ## Documentation
@@ -168,6 +175,8 @@ Runnable scripts in [examples/](examples/):
168
175
  | `12_kirshenbaum.py` | Kirshenbaum (ASCII-IPA) ↔ IPA, and ARPABET → Kirshenbaum routing |
169
176
  | `13_script_runs.py` | Per-script segmentation of mixed-script text |
170
177
  | `14_kana_transliteration.py` | Hiragana ↔ Katakana |
178
+ | `15_cotovia.py` | Cotovía (Galician TTS notation) ↔ IPA |
179
+ | `16_rfe.py` | RFE (Spanish/Romance philology alphabet) ↔ IPA |
171
180
 
172
181
  ## Fidelity guarantees
173
182
 
@@ -182,6 +191,8 @@ round-trip is exact and what happens to a symbol the table does not know.
182
191
  | **Buckwalter ↔ Arabic** | Exact (the `^` shadda alias normalises to canonical `~`; precomposed lam-alef ligatures decompose to two chars, visually identical) | Exact | Passed through unchanged |
183
192
  | **Lexique ↔ IPA** | Exact except the `°`/`3` schwa pair (both → `ə`; reverse always → `°`) | Exact | Passed through unchanged |
184
193
  | **Kirshenbaum ↔ IPA** | Exact | **Lossy** — restricted ASCII inventory; IPA outside it passes through | Passed through unchanged |
194
+ | **Cotovía ↔ IPA** | Exact except the three `L`/`Z`/`jj` symbols for `ʎ` normalise to `L` | **Lossy** — Galician/Spanish inventory; IPA outside it passes through | Passed through unchanged |
195
+ | **RFE ↔ IPA** | Exact except `ñ`/`n̮` for `ɲ` normalise to `ñ` | **Lossy** — core Spanish/Romance inventory; IPA outside it passes through | Passed through unchanged |
185
196
 
186
197
  This table is also available programmatically via `NOTATION_INFO`
187
198
  (`NotationInfo` records with `lossless_to_ipa`, `lossless_from_ipa`,
@@ -207,4 +218,6 @@ Derived tables used internally:
207
218
  | Buckwalter ↔ Arabic | Tim Buckwalter's Arabic transliteration scheme | — (factual 1:1 mapping) |
208
219
  | Lexique phoneme codes | New, B. & Pallier, C. — *Manuel de Lexique 3* v3.11, Tableau 2; [chrplr/openlexicon](https://github.com/chrplr/openlexicon) | CC BY-SA 4.0 |
209
220
  | Kirshenbaum ↔ IPA | Kirshenbaum 1993 ASCII-IPA standard (comp.speech), cross-checked against espeak-ng | — (factual symbol mapping) |
221
+ | Cotovía ↔ IPA | Universidade de Vigo GTM Cotovía TTS project (`fonemas.cpp`) | — (factual symbol mapping) |
222
+ | RFE ↔ IPA | RFE phonetic alphabet (Revista de Filología Española, 1915) | — (factual symbol mapping) |
210
223
  | Hangul jamo tables | [stannam/hangul_to_ipa](https://github.com/stannam/hangul_to_ipa) | — |
@@ -6,7 +6,7 @@
6
6
 
7
7
  **scriptconv** is a zero-dependency Python library for written-script operations:
8
8
  ISO-15924 script identification and metadata, character-range detection, language-to-script
9
- mapping, phoneme-notation transcoding (IPA ↔ ARPABET X-SAMPA, IPA Lexique,
9
+ mapping, phoneme-notation transcoding (IPA ↔ ARPABET / X-SAMPA / Lexique / Kirshenbaum / Cotovía / RFE,
10
10
  Buckwalter ↔ Arabic script), and orthographic decomposition of Hangul syllable blocks into
11
11
  jamo letters. Every conversion is a pure data table or arithmetic operation; no linguistic
12
12
  rules, no external files, no runtime dependencies beyond the Python standard library.
@@ -48,7 +48,8 @@ from scriptconv import (
48
48
  xsampa_to_ipa, ipa_to_xsampa,
49
49
  buckwalter_to_arabic, arabic_to_buckwalter,
50
50
  lexique_to_ipa, ipa_to_lexique,
51
- kirshenbaum_to_ipa, ipa_to_kirshenbaum, looks_like_ipa,
51
+ kirshenbaum_to_ipa, ipa_to_kirshenbaum, cotovia_to_ipa, ipa_to_cotovia,
52
+ rfe_to_ipa, ipa_to_rfe, looks_like_ipa,
52
53
  decompose_hangul, hira_to_kana, kana_to_hira,
53
54
  convert, can_convert, convert_batch, Notation, NOTATION_INFO,
54
55
  )
@@ -80,6 +81,12 @@ ipa_to_lexique("vɛ̃") # "v5" (vin)
80
81
  kirshenbaum_to_ipa("S") # "ʃ" (espeak-ng ASCII-IPA)
81
82
  ipa_to_kirshenbaum("ŋ") # "N"
82
83
 
84
+ cotovia_to_ipa("karro") # "karo" (Galician Cotovía TTS notation)
85
+ ipa_to_cotovia("ʎ") # "L"
86
+
87
+ rfe_to_ipa("kaša") # "kaʃa" (Spanish/Romance RFE alphabet)
88
+ ipa_to_rfe("ɲ") # "ñ"
89
+
83
90
  looks_like_ipa("pʰɑtʃ") # True (heuristic: has IPA-distinctive symbols)
84
91
  looks_like_ipa("hello") # False
85
92
 
@@ -112,7 +119,7 @@ python -m scriptconv lang ko
112
119
  | Module | Contents |
113
120
  |--------|----------|
114
121
  | `scriptconv.scripts` | `Script` dataclass (with `script_type`), `SCRIPT_REGISTRY` (34 scripts), `detect_script`, `char_script`, `script_distribution`, `script_runs`, `base_direction`, `lang_to_script`, `script_to_langs`, `normalize_script_tag` |
115
- | `scriptconv.notation` | `Notation` enum, `NotationInfo`/`NOTATION_INFO` fidelity registry, `convert` facade, `can_convert` predicate, `convert_batch` generator, pair-wise converters (ARPABET ↔ IPA, X-SAMPA ↔ IPA, Buckwalter ↔ Arabic, Lexique ↔ IPA, Kirshenbaum ↔ IPA), `looks_like_ipa` detector |
122
+ | `scriptconv.notation` | `Notation` enum, `NotationInfo`/`NOTATION_INFO` fidelity registry, `convert` facade, `can_convert` predicate, `convert_batch` generator, pair-wise converters (ARPABET ↔ IPA, X-SAMPA ↔ IPA, Buckwalter ↔ Arabic, Lexique ↔ IPA, Kirshenbaum ↔ IPA, Cotovía ↔ IPA, RFE ↔ IPA), `looks_like_ipa` detector |
116
123
  | `scriptconv.translit` | `decompose_hangul` (Hangul blocks → jamo, compatibility or conjoining), `hira_to_kana`/`kana_to_hira` — all orthographic only |
117
124
 
118
125
  ## Documentation
@@ -141,6 +148,8 @@ Runnable scripts in [examples/](examples/):
141
148
  | `12_kirshenbaum.py` | Kirshenbaum (ASCII-IPA) ↔ IPA, and ARPABET → Kirshenbaum routing |
142
149
  | `13_script_runs.py` | Per-script segmentation of mixed-script text |
143
150
  | `14_kana_transliteration.py` | Hiragana ↔ Katakana |
151
+ | `15_cotovia.py` | Cotovía (Galician TTS notation) ↔ IPA |
152
+ | `16_rfe.py` | RFE (Spanish/Romance philology alphabet) ↔ IPA |
144
153
 
145
154
  ## Fidelity guarantees
146
155
 
@@ -155,6 +164,8 @@ round-trip is exact and what happens to a symbol the table does not know.
155
164
  | **Buckwalter ↔ Arabic** | Exact (the `^` shadda alias normalises to canonical `~`; precomposed lam-alef ligatures decompose to two chars, visually identical) | Exact | Passed through unchanged |
156
165
  | **Lexique ↔ IPA** | Exact except the `°`/`3` schwa pair (both → `ə`; reverse always → `°`) | Exact | Passed through unchanged |
157
166
  | **Kirshenbaum ↔ IPA** | Exact | **Lossy** — restricted ASCII inventory; IPA outside it passes through | Passed through unchanged |
167
+ | **Cotovía ↔ IPA** | Exact except the three `L`/`Z`/`jj` symbols for `ʎ` normalise to `L` | **Lossy** — Galician/Spanish inventory; IPA outside it passes through | Passed through unchanged |
168
+ | **RFE ↔ IPA** | Exact except `ñ`/`n̮` for `ɲ` normalise to `ñ` | **Lossy** — core Spanish/Romance inventory; IPA outside it passes through | Passed through unchanged |
158
169
 
159
170
  This table is also available programmatically via `NOTATION_INFO`
160
171
  (`NotationInfo` records with `lossless_to_ipa`, `lossless_from_ipa`,
@@ -180,4 +191,6 @@ Derived tables used internally:
180
191
  | Buckwalter ↔ Arabic | Tim Buckwalter's Arabic transliteration scheme | — (factual 1:1 mapping) |
181
192
  | Lexique phoneme codes | New, B. & Pallier, C. — *Manuel de Lexique 3* v3.11, Tableau 2; [chrplr/openlexicon](https://github.com/chrplr/openlexicon) | CC BY-SA 4.0 |
182
193
  | Kirshenbaum ↔ IPA | Kirshenbaum 1993 ASCII-IPA standard (comp.speech), cross-checked against espeak-ng | — (factual symbol mapping) |
194
+ | Cotovía ↔ IPA | Universidade de Vigo GTM Cotovía TTS project (`fonemas.cpp`) | — (factual symbol mapping) |
195
+ | RFE ↔ IPA | RFE phonetic alphabet (Revista de Filología Española, 1915) | — (factual symbol mapping) |
183
196
  | Hangul jamo tables | [stannam/hangul_to_ipa](https://github.com/stannam/hangul_to_ipa) | — |
@@ -50,6 +50,10 @@ from scriptconv.notation import (
50
50
  ipa_to_lexique,
51
51
  kirshenbaum_to_ipa,
52
52
  ipa_to_kirshenbaum,
53
+ cotovia_to_ipa,
54
+ ipa_to_cotovia,
55
+ rfe_to_ipa,
56
+ ipa_to_rfe,
53
57
  looks_like_ipa,
54
58
  )
55
59
  from scriptconv.translit import (
@@ -87,6 +91,10 @@ __all__ = [
87
91
  "ipa_to_lexique",
88
92
  "kirshenbaum_to_ipa",
89
93
  "ipa_to_kirshenbaum",
94
+ "cotovia_to_ipa",
95
+ "ipa_to_cotovia",
96
+ "rfe_to_ipa",
97
+ "ipa_to_rfe",
90
98
  "looks_like_ipa",
91
99
  # translit
92
100
  "decompose_hangul",
@@ -37,6 +37,10 @@ __all__ = [
37
37
  "ipa_to_lexique",
38
38
  "kirshenbaum_to_ipa",
39
39
  "ipa_to_kirshenbaum",
40
+ "cotovia_to_ipa",
41
+ "ipa_to_cotovia",
42
+ "rfe_to_ipa",
43
+ "ipa_to_rfe",
40
44
  "looks_like_ipa",
41
45
  ]
42
46
 
@@ -51,6 +55,8 @@ class Notation(str, Enum):
51
55
  ARABIC = "arabic" # Arabic script (target/source for BW)
52
56
  LEXIQUE = "lexique" # Lexique one-char-per-phoneme French notation
53
57
  KIRSHENBAUM = "kirshenbaum" # ASCII-IPA (espeak-ng native notation)
58
+ COTOVIA = "cotovia" # Universidade de Vigo Cotovía TTS notation (gl)
59
+ RFE = "rfe" # Revista de Filología Española phonetic alphabet
54
60
 
55
61
  def __repr__(self) -> str:
56
62
  return f"Notation.{self.name}"
@@ -665,6 +671,244 @@ def ipa_to_kirshenbaum(ipa: str) -> str:
665
671
  return "".join(_IPA_TO_KIRSHENBAUM.get(ch, ch) for ch in ipa)
666
672
 
667
673
 
674
+ # ---------------------------------------------------------------------------
675
+ # Cotovía ↔ IPA
676
+ #
677
+ # Cotovía is the internal phoneme notation of the Universidade de Vigo GTM
678
+ # Cotovía TTS system (Galician/Spanish). The table is the phoneme symbol map
679
+ # from its ``fonemas.cpp``. Symbols are matched longest-first (``tS``, ``rr``
680
+ # before single characters). The silence/pause marker (``#``) is a boundary
681
+ # token, not a phoneme, and is intentionally excluded.
682
+ # ---------------------------------------------------------------------------
683
+
684
+ _COTOVIA_TO_IPA: dict[str, str] = {
685
+ # Double-character symbols first (longest-match ordering)
686
+ "tS": "tʃ",
687
+ "rr": "r", # trill (single "r" is the tap ɾ)
688
+ "jj": "ʎ",
689
+ # Consonants
690
+ "B": "β",
691
+ "D": "ð",
692
+ "G": "ɣ",
693
+ "J": "ɲ",
694
+ "N": "ŋ",
695
+ "S": "ʃ",
696
+ "T": "θ",
697
+ "Z": "ʎ",
698
+ "L": "ʎ", # canonical Cotovía symbol for the palatal lateral
699
+ "b": "b",
700
+ "d": "d",
701
+ "f": "f",
702
+ "g": "ɡ",
703
+ "k": "k",
704
+ "l": "l",
705
+ "m": "m",
706
+ "n": "n",
707
+ "p": "p",
708
+ "r": "ɾ", # tap
709
+ "s": "s",
710
+ "t": "t",
711
+ "x": "x",
712
+ "X": "x",
713
+ # Vowels
714
+ "E": "ɛ",
715
+ "O": "ɔ",
716
+ "a": "a",
717
+ "e": "e",
718
+ "i": "i",
719
+ "o": "o",
720
+ "u": "u",
721
+ # Glides
722
+ "j": "j",
723
+ "w": "w",
724
+ }
725
+
726
+ # Reverse: IPA → Cotovía. First-wins deduplicates the symbols that share an IPA
727
+ # value; pin ʎ → "L" (the standard Cotovía palatal-lateral symbol) and x → "x".
728
+ _IPA_TO_COTOVIA: dict[str, str] = {}
729
+ for _cv, _ip in _COTOVIA_TO_IPA.items():
730
+ _IPA_TO_COTOVIA.setdefault(_ip, _cv)
731
+ _IPA_TO_COTOVIA["ʎ"] = "L"
732
+ _IPA_TO_COTOVIA["x"] = "x"
733
+
734
+ _CV_KEYS_SORTED = sorted(_COTOVIA_TO_IPA.keys(), key=len, reverse=True)
735
+ _CV_RE = re.compile("|".join(re.escape(s) for s in _CV_KEYS_SORTED))
736
+ _IPA_CV_KEYS_SORTED = sorted(_IPA_TO_COTOVIA.keys(), key=len, reverse=True)
737
+ _IPA_CV_RE = re.compile("|".join(re.escape(s) for s in _IPA_CV_KEYS_SORTED))
738
+
739
+
740
+ def cotovia_to_ipa(cotovia: str) -> str:
741
+ """Convert a Cotovía phoneme string to IPA.
742
+
743
+ Multi-character symbols are matched longest-first. Characters outside the
744
+ table pass through unchanged.
745
+
746
+ Examples
747
+ --------
748
+ >>> cotovia_to_ipa("tS")
749
+ 'tʃ'
750
+ >>> cotovia_to_ipa("karro")
751
+ 'karo'
752
+ """
753
+ result = []
754
+ pos = 0
755
+ while pos < len(cotovia):
756
+ m = _CV_RE.match(cotovia, pos)
757
+ if m:
758
+ result.append(_COTOVIA_TO_IPA[m.group(0)])
759
+ pos = m.end()
760
+ else:
761
+ result.append(cotovia[pos])
762
+ pos += 1
763
+ return "".join(result)
764
+
765
+
766
+ def ipa_to_cotovia(ipa: str) -> str:
767
+ """Convert an IPA string to Cotovía notation.
768
+
769
+ Multi-character IPA symbols are matched longest-first. Characters outside
770
+ the table pass through unchanged.
771
+
772
+ Examples
773
+ --------
774
+ >>> ipa_to_cotovia("tʃ")
775
+ 'tS'
776
+ >>> ipa_to_cotovia("ʎ")
777
+ 'L'
778
+ """
779
+ result = []
780
+ pos = 0
781
+ while pos < len(ipa):
782
+ m = _IPA_CV_RE.match(ipa, pos)
783
+ if m:
784
+ result.append(_IPA_TO_COTOVIA[m.group(0)])
785
+ pos = m.end()
786
+ else:
787
+ result.append(ipa[pos])
788
+ pos += 1
789
+ return "".join(result)
790
+
791
+
792
+ # ---------------------------------------------------------------------------
793
+ # RFE (Revista de Filología Española) ↔ IPA
794
+ #
795
+ # The RFE phonetic alphabet (Navarro Tomás, Revista de Filología Española,
796
+ # 1915) is the traditional transcription system of Spanish and Romance
797
+ # philology. This is its core, well-attested inventory; rare interdental and
798
+ # variant diacritic symbols from the full standard are omitted. Symbols are
799
+ # matched longest-first (combining-mark symbols before their base letters).
800
+ # Reference: RFE Phonetic Alphabet
801
+ # (https://en.wikipedia.org/wiki/RFE_Phonetic_Alphabet).
802
+ # ---------------------------------------------------------------------------
803
+
804
+ _RFE_TO_IPA: dict[str, str] = {
805
+ # Combining-mark symbols (longest-match first)
806
+ "r̄": "r", # r + macron = alveolar trill (plain r = tap ɾ)
807
+ "n̮": "ɲ", # n + breve below = palatal nasal ɲ
808
+ "l̮": "ʎ", # l + breve below = palatal lateral ʎ
809
+ # Palatal / postalveolar
810
+ "ĉ": "tʃ", # ĉ = tʃ
811
+ "š": "ʃ", # š = ʃ
812
+ "ž": "ʒ", # ž = ʒ
813
+ "ŷ": "ɟʝ", # ŷ = ɟʝ (voiced palatal affricate)
814
+ "ñ": "ɲ", # ñ = ɲ
815
+ "y": "ʝ", # y = ʝ
816
+ "j": "j", # j = j
817
+ # Spanish approximant/fricative allophones
818
+ "ƀ": "β", # ƀ = β
819
+ "đ": "ð", # đ = ð
820
+ "ǥ": "ɣ", # ǥ = ɣ
821
+ # Other consonants
822
+ "θ": "θ", # θ = θ
823
+ "x": "x",
824
+ "ł": "ɫ", # ł = ɫ (velarized l)
825
+ "ŋ": "ŋ", # ŋ = ŋ
826
+ "w": "w",
827
+ "h": "h",
828
+ "b": "b",
829
+ "p": "p",
830
+ "t": "t",
831
+ "d": "d",
832
+ "k": "k",
833
+ "g": "ɡ", # g = ɡ (script g, matching the other tables)
834
+ "f": "f",
835
+ "v": "v",
836
+ "s": "s",
837
+ "z": "z",
838
+ "m": "m",
839
+ "n": "n",
840
+ "l": "l",
841
+ "r": "ɾ", # r = tap ɾ
842
+ # Vowels
843
+ "a": "a", "e": "e", "i": "i", "o": "o", "u": "u",
844
+ "ə": "ə", # ə = ə
845
+ }
846
+
847
+ # Reverse: IPA → RFE. First-wins deduplicates; pin the canonical RFE spellings
848
+ # for the palatal nasal (ñ) and the trill/tap pair.
849
+ _IPA_TO_RFE: dict[str, str] = {}
850
+ for _rfe, _ip in _RFE_TO_IPA.items():
851
+ _IPA_TO_RFE.setdefault(_ip, _rfe)
852
+ _IPA_TO_RFE["ɲ"] = "ñ" # ɲ → ñ (canonical, not the n̮ variant)
853
+
854
+ _RFE_KEYS_SORTED = sorted(_RFE_TO_IPA.keys(), key=len, reverse=True)
855
+ _RFE_RE = re.compile("|".join(re.escape(x) for x in _RFE_KEYS_SORTED))
856
+ _IPA_RFE_KEYS_SORTED = sorted(_IPA_TO_RFE.keys(), key=len, reverse=True)
857
+ _IPA_RFE_RE = re.compile("|".join(re.escape(x) for x in _IPA_RFE_KEYS_SORTED))
858
+
859
+
860
+ def rfe_to_ipa(rfe: str) -> str:
861
+ """Convert an RFE (Revista de Filología Española) string to IPA.
862
+
863
+ Multi-character symbols are matched longest-first. Characters outside the
864
+ table pass through unchanged.
865
+
866
+ Examples
867
+ --------
868
+ >>> rfe_to_ipa("š")
869
+ 'ʃ'
870
+ >>> rfe_to_ipa("kaša")
871
+ 'kaʃa'
872
+ """
873
+ result = []
874
+ pos = 0
875
+ while pos < len(rfe):
876
+ m = _RFE_RE.match(rfe, pos)
877
+ if m:
878
+ result.append(_RFE_TO_IPA[m.group(0)])
879
+ pos = m.end()
880
+ else:
881
+ result.append(rfe[pos])
882
+ pos += 1
883
+ return "".join(result)
884
+
885
+
886
+ def ipa_to_rfe(ipa: str) -> str:
887
+ """Convert an IPA string to RFE (Revista de Filología Española) notation.
888
+
889
+ Multi-character IPA symbols are matched longest-first. Characters outside
890
+ the table pass through unchanged.
891
+
892
+ Examples
893
+ --------
894
+ >>> ipa_to_rfe("ʃ")
895
+ 'š'
896
+ >>> ipa_to_rfe("ɲ")
897
+ 'ñ'
898
+ """
899
+ result = []
900
+ pos = 0
901
+ while pos < len(ipa):
902
+ m = _IPA_RFE_RE.match(ipa, pos)
903
+ if m:
904
+ result.append(_IPA_TO_RFE[m.group(0)])
905
+ pos = m.end()
906
+ else:
907
+ result.append(ipa[pos])
908
+ pos += 1
909
+ return "".join(result)
910
+
911
+
668
912
  # ---------------------------------------------------------------------------
669
913
  # convert — facade routing through IPA where no direct map exists
670
914
  # ---------------------------------------------------------------------------
@@ -735,12 +979,16 @@ _TO_IPA: dict[Notation, "callable"] = {
735
979
  Notation.XSAMPA: xsampa_to_ipa,
736
980
  Notation.LEXIQUE: lexique_to_ipa,
737
981
  Notation.KIRSHENBAUM: kirshenbaum_to_ipa,
982
+ Notation.COTOVIA: cotovia_to_ipa,
983
+ Notation.RFE: rfe_to_ipa,
738
984
  }
739
985
  _FROM_IPA: dict[Notation, "callable"] = {
740
986
  Notation.ARPA: ipa_to_arpa,
741
987
  Notation.XSAMPA: ipa_to_xsampa,
742
988
  Notation.LEXIQUE: ipa_to_lexique,
743
989
  Notation.KIRSHENBAUM: ipa_to_kirshenbaum,
990
+ Notation.COTOVIA: ipa_to_cotovia,
991
+ Notation.RFE: ipa_to_rfe,
744
992
  }
745
993
  _DIRECT_PAIRS: dict[tuple[Notation, Notation], "callable"] = {
746
994
  (Notation.BUCKWALTER, Notation.ARABIC): buckwalter_to_arabic,
@@ -846,6 +1094,16 @@ NOTATION_INFO: dict[Notation, NotationInfo] = {
846
1094
  token_separated=False,
847
1095
  reference="Kirshenbaum 1993 ASCII-IPA standard (comp.speech)",
848
1096
  ),
1097
+ Notation.COTOVIA: NotationInfo(
1098
+ Notation.COTOVIA, lossless_to_ipa=False, lossless_from_ipa=False,
1099
+ token_separated=False,
1100
+ reference="Universidade de Vigo GTM Cotovía TTS (fonemas.cpp)",
1101
+ ),
1102
+ Notation.RFE: NotationInfo(
1103
+ Notation.RFE, lossless_to_ipa=False, lossless_from_ipa=False,
1104
+ token_separated=False,
1105
+ reference="RFE phonetic alphabet (Revista de Filología Española, 1915)",
1106
+ ),
849
1107
  Notation.BUCKWALTER: NotationInfo(
850
1108
  Notation.BUCKWALTER, lossless_to_ipa=True, lossless_from_ipa=False,
851
1109
  token_separated=False,
@@ -1,8 +1,8 @@
1
1
  # START_VERSION_BLOCK
2
2
  VERSION_MAJOR = 0
3
3
  VERSION_MINOR = 0
4
- VERSION_BUILD = 1
5
- VERSION_ALPHA = 6
4
+ VERSION_BUILD = 2
5
+ VERSION_ALPHA = 2
6
6
  # END_VERSION_BLOCK
7
7
 
8
8
  VERSION_STR = f"{VERSION_MAJOR}.{VERSION_MINOR}.{VERSION_BUILD}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scriptconv
3
- Version: 0.0.1a6
3
+ Version: 0.0.2a2
4
4
  Summary: Shared script-conversion core — ISO-15924 detection, IPA↔ARPA/X-SAMPA/Lexique, Buckwalter, Hangul→jamo
5
5
  License: Apache-2.0
6
6
  Project-URL: Homepage, https://github.com/TigreGotico/scriptconv
@@ -33,7 +33,7 @@ Dynamic: license-file
33
33
 
34
34
  **scriptconv** is a zero-dependency Python library for written-script operations:
35
35
  ISO-15924 script identification and metadata, character-range detection, language-to-script
36
- mapping, phoneme-notation transcoding (IPA ↔ ARPABET X-SAMPA, IPA Lexique,
36
+ mapping, phoneme-notation transcoding (IPA ↔ ARPABET / X-SAMPA / Lexique / Kirshenbaum / Cotovía / RFE,
37
37
  Buckwalter ↔ Arabic script), and orthographic decomposition of Hangul syllable blocks into
38
38
  jamo letters. Every conversion is a pure data table or arithmetic operation; no linguistic
39
39
  rules, no external files, no runtime dependencies beyond the Python standard library.
@@ -75,7 +75,8 @@ from scriptconv import (
75
75
  xsampa_to_ipa, ipa_to_xsampa,
76
76
  buckwalter_to_arabic, arabic_to_buckwalter,
77
77
  lexique_to_ipa, ipa_to_lexique,
78
- kirshenbaum_to_ipa, ipa_to_kirshenbaum, looks_like_ipa,
78
+ kirshenbaum_to_ipa, ipa_to_kirshenbaum, cotovia_to_ipa, ipa_to_cotovia,
79
+ rfe_to_ipa, ipa_to_rfe, looks_like_ipa,
79
80
  decompose_hangul, hira_to_kana, kana_to_hira,
80
81
  convert, can_convert, convert_batch, Notation, NOTATION_INFO,
81
82
  )
@@ -107,6 +108,12 @@ ipa_to_lexique("vɛ̃") # "v5" (vin)
107
108
  kirshenbaum_to_ipa("S") # "ʃ" (espeak-ng ASCII-IPA)
108
109
  ipa_to_kirshenbaum("ŋ") # "N"
109
110
 
111
+ cotovia_to_ipa("karro") # "karo" (Galician Cotovía TTS notation)
112
+ ipa_to_cotovia("ʎ") # "L"
113
+
114
+ rfe_to_ipa("kaša") # "kaʃa" (Spanish/Romance RFE alphabet)
115
+ ipa_to_rfe("ɲ") # "ñ"
116
+
110
117
  looks_like_ipa("pʰɑtʃ") # True (heuristic: has IPA-distinctive symbols)
111
118
  looks_like_ipa("hello") # False
112
119
 
@@ -139,7 +146,7 @@ python -m scriptconv lang ko
139
146
  | Module | Contents |
140
147
  |--------|----------|
141
148
  | `scriptconv.scripts` | `Script` dataclass (with `script_type`), `SCRIPT_REGISTRY` (34 scripts), `detect_script`, `char_script`, `script_distribution`, `script_runs`, `base_direction`, `lang_to_script`, `script_to_langs`, `normalize_script_tag` |
142
- | `scriptconv.notation` | `Notation` enum, `NotationInfo`/`NOTATION_INFO` fidelity registry, `convert` facade, `can_convert` predicate, `convert_batch` generator, pair-wise converters (ARPABET ↔ IPA, X-SAMPA ↔ IPA, Buckwalter ↔ Arabic, Lexique ↔ IPA, Kirshenbaum ↔ IPA), `looks_like_ipa` detector |
149
+ | `scriptconv.notation` | `Notation` enum, `NotationInfo`/`NOTATION_INFO` fidelity registry, `convert` facade, `can_convert` predicate, `convert_batch` generator, pair-wise converters (ARPABET ↔ IPA, X-SAMPA ↔ IPA, Buckwalter ↔ Arabic, Lexique ↔ IPA, Kirshenbaum ↔ IPA, Cotovía ↔ IPA, RFE ↔ IPA), `looks_like_ipa` detector |
143
150
  | `scriptconv.translit` | `decompose_hangul` (Hangul blocks → jamo, compatibility or conjoining), `hira_to_kana`/`kana_to_hira` — all orthographic only |
144
151
 
145
152
  ## Documentation
@@ -168,6 +175,8 @@ Runnable scripts in [examples/](examples/):
168
175
  | `12_kirshenbaum.py` | Kirshenbaum (ASCII-IPA) ↔ IPA, and ARPABET → Kirshenbaum routing |
169
176
  | `13_script_runs.py` | Per-script segmentation of mixed-script text |
170
177
  | `14_kana_transliteration.py` | Hiragana ↔ Katakana |
178
+ | `15_cotovia.py` | Cotovía (Galician TTS notation) ↔ IPA |
179
+ | `16_rfe.py` | RFE (Spanish/Romance philology alphabet) ↔ IPA |
171
180
 
172
181
  ## Fidelity guarantees
173
182
 
@@ -182,6 +191,8 @@ round-trip is exact and what happens to a symbol the table does not know.
182
191
  | **Buckwalter ↔ Arabic** | Exact (the `^` shadda alias normalises to canonical `~`; precomposed lam-alef ligatures decompose to two chars, visually identical) | Exact | Passed through unchanged |
183
192
  | **Lexique ↔ IPA** | Exact except the `°`/`3` schwa pair (both → `ə`; reverse always → `°`) | Exact | Passed through unchanged |
184
193
  | **Kirshenbaum ↔ IPA** | Exact | **Lossy** — restricted ASCII inventory; IPA outside it passes through | Passed through unchanged |
194
+ | **Cotovía ↔ IPA** | Exact except the three `L`/`Z`/`jj` symbols for `ʎ` normalise to `L` | **Lossy** — Galician/Spanish inventory; IPA outside it passes through | Passed through unchanged |
195
+ | **RFE ↔ IPA** | Exact except `ñ`/`n̮` for `ɲ` normalise to `ñ` | **Lossy** — core Spanish/Romance inventory; IPA outside it passes through | Passed through unchanged |
185
196
 
186
197
  This table is also available programmatically via `NOTATION_INFO`
187
198
  (`NotationInfo` records with `lossless_to_ipa`, `lossless_from_ipa`,
@@ -207,4 +218,6 @@ Derived tables used internally:
207
218
  | Buckwalter ↔ Arabic | Tim Buckwalter's Arabic transliteration scheme | — (factual 1:1 mapping) |
208
219
  | Lexique phoneme codes | New, B. & Pallier, C. — *Manuel de Lexique 3* v3.11, Tableau 2; [chrplr/openlexicon](https://github.com/chrplr/openlexicon) | CC BY-SA 4.0 |
209
220
  | Kirshenbaum ↔ IPA | Kirshenbaum 1993 ASCII-IPA standard (comp.speech), cross-checked against espeak-ng | — (factual symbol mapping) |
221
+ | Cotovía ↔ IPA | Universidade de Vigo GTM Cotovía TTS project (`fonemas.cpp`) | — (factual symbol mapping) |
222
+ | RFE ↔ IPA | RFE phonetic alphabet (Revista de Filología Española, 1915) | — (factual symbol mapping) |
210
223
  | Hangul jamo tables | [stannam/hangul_to_ipa](https://github.com/stannam/hangul_to_ipa) | — |
@@ -105,3 +105,15 @@ def test_14_kana_transliteration():
105
105
  r = _run(EXAMPLES_DIR / "14_kana_transliteration.py")
106
106
  assert r.returncode == 0, r.stderr
107
107
  assert r.stdout.strip()
108
+
109
+
110
+ def test_15_cotovia():
111
+ r = _run(EXAMPLES_DIR / "15_cotovia.py")
112
+ assert r.returncode == 0, r.stderr
113
+ assert r.stdout.strip()
114
+
115
+
116
+ def test_16_rfe():
117
+ r = _run(EXAMPLES_DIR / "16_rfe.py")
118
+ assert r.returncode == 0, r.stderr
119
+ assert r.stdout.strip()
@@ -947,7 +947,7 @@ def test_notation_enum_values():
947
947
 
948
948
 
949
949
  def test_notation_enum_count():
950
- assert len(Notation) == 7
950
+ assert len(Notation) == 9
951
951
 
952
952
 
953
953
  def test_notation_str():
@@ -1057,3 +1057,94 @@ def test_looks_like_ipa_true(text):
1057
1057
  ])
1058
1058
  def test_looks_like_ipa_false(text):
1059
1059
  assert looks_like_ipa(text) is False
1060
+
1061
+
1062
+ # ---------------------------------------------------------------------------
1063
+ # Cotovía ↔ IPA
1064
+ # ---------------------------------------------------------------------------
1065
+
1066
+ from scriptconv.notation import cotovia_to_ipa, ipa_to_cotovia # noqa: E402
1067
+
1068
+
1069
+ @pytest.mark.parametrize("cv, ipa", [
1070
+ ("tS", "tʃ"),
1071
+ ("rr", "r"), # trill
1072
+ ("r", "ɾ"), # tap
1073
+ ("L", "ʎ"), ("Z", "ʎ"), ("jj", "ʎ"), # all three collapse to ʎ
1074
+ ("B", "β"), ("D", "ð"), ("G", "ɣ"), ("J", "ɲ"), ("N", "ŋ"),
1075
+ ("S", "ʃ"), ("T", "θ"), ("E", "ɛ"), ("O", "ɔ"), ("x", "x"), ("X", "x"),
1076
+ ("karro", "karo"),
1077
+ ])
1078
+ def test_cotovia_to_ipa(cv, ipa):
1079
+ assert cotovia_to_ipa(cv) == ipa
1080
+
1081
+
1082
+ @pytest.mark.parametrize("ipa, cv", [
1083
+ ("tʃ", "tS"),
1084
+ ("r", "rr"), # trill
1085
+ ("ɾ", "r"), # tap
1086
+ ("ʎ", "L"), # canonical Cotovía palatal lateral
1087
+ ("x", "x"),
1088
+ ("β", "B"), ("ɲ", "J"), ("ɛ", "E"), ("ɔ", "O"),
1089
+ ])
1090
+ def test_ipa_to_cotovia(ipa, cv):
1091
+ assert ipa_to_cotovia(ipa) == cv
1092
+
1093
+
1094
+ def test_cotovia_tap_trill_round_trip():
1095
+ # The tap/trill distinction must survive a round-trip.
1096
+ assert ipa_to_cotovia(cotovia_to_ipa("karro")) == "karro" # trill
1097
+ assert ipa_to_cotovia(cotovia_to_ipa("kara")) == "kara" # tap
1098
+
1099
+
1100
+ def test_convert_routes_through_cotovia():
1101
+ assert convert("tS", "cotovia", "x-sampa") == "tS"
1102
+ assert convert("CH IY1", "arpa", "cotovia") == "tSi"
1103
+
1104
+
1105
+ def test_cotovia_pause_marker_not_a_phoneme():
1106
+ # "#" (silence marker) is excluded from the table and passes through
1107
+ # unchanged, rather than being emitted as a "pau" token.
1108
+ assert cotovia_to_ipa("#") == "#"
1109
+ # p/a/u are ordinary phonemes, converted independently.
1110
+ assert cotovia_to_ipa("pau") == "pau"
1111
+
1112
+
1113
+ # ---------------------------------------------------------------------------
1114
+ # RFE (Revista de Filología Española) ↔ IPA
1115
+ # ---------------------------------------------------------------------------
1116
+
1117
+ from scriptconv.notation import rfe_to_ipa, ipa_to_rfe # noqa: E402
1118
+
1119
+
1120
+ @pytest.mark.parametrize("rfe, ipa", [
1121
+ ("š", "ʃ"), ("ž", "ʒ"), ("ĉ", "tʃ"), ("y", "ʝ"), ("ŷ", "ɟʝ"),
1122
+ ("ñ", "ɲ"), ("n̮", "ɲ"), ("l̮", "ʎ"),
1123
+ ("ƀ", "β"), ("đ", "ð"), ("ǥ", "ɣ"), ("θ", "θ"), ("ł", "ɫ"),
1124
+ ("r", "ɾ"), ("r̄", "r"),
1125
+ ("g", "ɡ"),
1126
+ ("kaša", "kaʃa"),
1127
+ ])
1128
+ def test_rfe_to_ipa(rfe, ipa):
1129
+ assert rfe_to_ipa(rfe) == ipa
1130
+
1131
+
1132
+ @pytest.mark.parametrize("ipa, rfe", [
1133
+ ("ʃ", "š"), ("ʒ", "ž"), ("tʃ", "ĉ"), ("ʝ", "y"),
1134
+ ("ɲ", "ñ"), # canonical, not the n̮ variant
1135
+ ("ʎ", "l̮"),
1136
+ ("β", "ƀ"), ("ð", "đ"), ("ɣ", "ǥ"),
1137
+ ("ɾ", "r"), ("r", "r̄"),
1138
+ ])
1139
+ def test_ipa_to_rfe(ipa, rfe):
1140
+ assert ipa_to_rfe(ipa) == rfe
1141
+
1142
+
1143
+ def test_rfe_tap_trill_round_trip():
1144
+ assert ipa_to_rfe(rfe_to_ipa("far̄a")) == "far̄a" # trill
1145
+ assert ipa_to_rfe(rfe_to_ipa("kara")) == "kara" # tap
1146
+
1147
+
1148
+ def test_convert_routes_through_rfe():
1149
+ assert convert("š", "rfe", "x-sampa") == "S"
1150
+ assert convert("ʃ", "ipa", "rfe") == "š"
File without changes
File without changes