scriptconv 0.0.1a5__tar.gz → 0.0.2a1__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.1a5 → scriptconv-0.0.2a1}/PKG-INFO +13 -4
  2. {scriptconv-0.0.1a5 → scriptconv-0.0.2a1}/README.md +12 -3
  3. {scriptconv-0.0.1a5 → scriptconv-0.0.2a1}/scriptconv/__init__.py +6 -0
  4. {scriptconv-0.0.1a5 → scriptconv-0.0.2a1}/scriptconv/notation.py +182 -0
  5. {scriptconv-0.0.1a5 → scriptconv-0.0.2a1}/scriptconv/version.py +2 -2
  6. {scriptconv-0.0.1a5 → scriptconv-0.0.2a1}/scriptconv.egg-info/PKG-INFO +13 -4
  7. {scriptconv-0.0.1a5 → scriptconv-0.0.2a1}/tests/test_examples.py +6 -0
  8. {scriptconv-0.0.1a5 → scriptconv-0.0.2a1}/tests/test_notation.py +82 -1
  9. {scriptconv-0.0.1a5 → scriptconv-0.0.2a1}/LICENSE +0 -0
  10. {scriptconv-0.0.1a5 → scriptconv-0.0.2a1}/pyproject.toml +0 -0
  11. {scriptconv-0.0.1a5 → scriptconv-0.0.2a1}/requirements.txt +0 -0
  12. {scriptconv-0.0.1a5 → scriptconv-0.0.2a1}/scriptconv/__main__.py +0 -0
  13. {scriptconv-0.0.1a5 → scriptconv-0.0.2a1}/scriptconv/py.typed +0 -0
  14. {scriptconv-0.0.1a5 → scriptconv-0.0.2a1}/scriptconv/scripts.py +0 -0
  15. {scriptconv-0.0.1a5 → scriptconv-0.0.2a1}/scriptconv/translit.py +0 -0
  16. {scriptconv-0.0.1a5 → scriptconv-0.0.2a1}/scriptconv.egg-info/SOURCES.txt +0 -0
  17. {scriptconv-0.0.1a5 → scriptconv-0.0.2a1}/scriptconv.egg-info/dependency_links.txt +0 -0
  18. {scriptconv-0.0.1a5 → scriptconv-0.0.2a1}/scriptconv.egg-info/requires.txt +0 -0
  19. {scriptconv-0.0.1a5 → scriptconv-0.0.2a1}/scriptconv.egg-info/top_level.txt +0 -0
  20. {scriptconv-0.0.1a5 → scriptconv-0.0.2a1}/setup.cfg +0 -0
  21. {scriptconv-0.0.1a5 → scriptconv-0.0.2a1}/tests/test_cli.py +0 -0
  22. {scriptconv-0.0.1a5 → scriptconv-0.0.2a1}/tests/test_scripts.py +0 -0
  23. {scriptconv-0.0.1a5 → scriptconv-0.0.2a1}/tests/test_translit.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scriptconv
3
- Version: 0.0.1a5
3
+ Version: 0.0.2a1
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,
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,7 @@ 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,
78
+ kirshenbaum_to_ipa, ipa_to_kirshenbaum, cotovia_to_ipa, ipa_to_cotovia, looks_like_ipa,
79
79
  decompose_hangul, hira_to_kana, kana_to_hira,
80
80
  convert, can_convert, convert_batch, Notation, NOTATION_INFO,
81
81
  )
@@ -107,6 +107,12 @@ ipa_to_lexique("vɛ̃") # "v5" (vin)
107
107
  kirshenbaum_to_ipa("S") # "ʃ" (espeak-ng ASCII-IPA)
108
108
  ipa_to_kirshenbaum("ŋ") # "N"
109
109
 
110
+ cotovia_to_ipa("karro") # "karo" (Galician Cotovía TTS notation)
111
+ ipa_to_cotovia("ʎ") # "L"
112
+
113
+ looks_like_ipa("pʰɑtʃ") # True (heuristic: has IPA-distinctive symbols)
114
+ looks_like_ipa("hello") # False
115
+
110
116
  decompose_hangul("국민") # "ㄱㅜㄱㅁㅣㄴ" (orthographic jamo, no assimilation)
111
117
  hira_to_kana("ひらがな") # "ヒラガナ"
112
118
  kana_to_hira("カタカナ") # "かたかな"
@@ -136,7 +142,7 @@ python -m scriptconv lang ko
136
142
  | Module | Contents |
137
143
  |--------|----------|
138
144
  | `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` |
139
- | `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) |
145
+ | `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), `looks_like_ipa` detector |
140
146
  | `scriptconv.translit` | `decompose_hangul` (Hangul blocks → jamo, compatibility or conjoining), `hira_to_kana`/`kana_to_hira` — all orthographic only |
141
147
 
142
148
  ## Documentation
@@ -165,6 +171,7 @@ Runnable scripts in [examples/](examples/):
165
171
  | `12_kirshenbaum.py` | Kirshenbaum (ASCII-IPA) ↔ IPA, and ARPABET → Kirshenbaum routing |
166
172
  | `13_script_runs.py` | Per-script segmentation of mixed-script text |
167
173
  | `14_kana_transliteration.py` | Hiragana ↔ Katakana |
174
+ | `15_cotovia.py` | Cotovía (Galician TTS notation) ↔ IPA |
168
175
 
169
176
  ## Fidelity guarantees
170
177
 
@@ -179,6 +186,7 @@ round-trip is exact and what happens to a symbol the table does not know.
179
186
  | **Buckwalter ↔ Arabic** | Exact (the `^` shadda alias normalises to canonical `~`; precomposed lam-alef ligatures decompose to two chars, visually identical) | Exact | Passed through unchanged |
180
187
  | **Lexique ↔ IPA** | Exact except the `°`/`3` schwa pair (both → `ə`; reverse always → `°`) | Exact | Passed through unchanged |
181
188
  | **Kirshenbaum ↔ IPA** | Exact | **Lossy** — restricted ASCII inventory; IPA outside it passes through | Passed through unchanged |
189
+ | **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 |
182
190
 
183
191
  This table is also available programmatically via `NOTATION_INFO`
184
192
  (`NotationInfo` records with `lossless_to_ipa`, `lossless_from_ipa`,
@@ -204,4 +212,5 @@ Derived tables used internally:
204
212
  | Buckwalter ↔ Arabic | Tim Buckwalter's Arabic transliteration scheme | — (factual 1:1 mapping) |
205
213
  | 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 |
206
214
  | Kirshenbaum ↔ IPA | Kirshenbaum 1993 ASCII-IPA standard (comp.speech), cross-checked against espeak-ng | — (factual symbol mapping) |
215
+ | Cotovía ↔ IPA | Universidade de Vigo GTM Cotovía TTS project (`fonemas.cpp`) | — (factual symbol mapping) |
207
216
  | 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,
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,7 @@ 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,
51
+ kirshenbaum_to_ipa, ipa_to_kirshenbaum, cotovia_to_ipa, ipa_to_cotovia, looks_like_ipa,
52
52
  decompose_hangul, hira_to_kana, kana_to_hira,
53
53
  convert, can_convert, convert_batch, Notation, NOTATION_INFO,
54
54
  )
@@ -80,6 +80,12 @@ ipa_to_lexique("vɛ̃") # "v5" (vin)
80
80
  kirshenbaum_to_ipa("S") # "ʃ" (espeak-ng ASCII-IPA)
81
81
  ipa_to_kirshenbaum("ŋ") # "N"
82
82
 
83
+ cotovia_to_ipa("karro") # "karo" (Galician Cotovía TTS notation)
84
+ ipa_to_cotovia("ʎ") # "L"
85
+
86
+ looks_like_ipa("pʰɑtʃ") # True (heuristic: has IPA-distinctive symbols)
87
+ looks_like_ipa("hello") # False
88
+
83
89
  decompose_hangul("국민") # "ㄱㅜㄱㅁㅣㄴ" (orthographic jamo, no assimilation)
84
90
  hira_to_kana("ひらがな") # "ヒラガナ"
85
91
  kana_to_hira("カタカナ") # "かたかな"
@@ -109,7 +115,7 @@ python -m scriptconv lang ko
109
115
  | Module | Contents |
110
116
  |--------|----------|
111
117
  | `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` |
112
- | `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) |
118
+ | `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), `looks_like_ipa` detector |
113
119
  | `scriptconv.translit` | `decompose_hangul` (Hangul blocks → jamo, compatibility or conjoining), `hira_to_kana`/`kana_to_hira` — all orthographic only |
114
120
 
115
121
  ## Documentation
@@ -138,6 +144,7 @@ Runnable scripts in [examples/](examples/):
138
144
  | `12_kirshenbaum.py` | Kirshenbaum (ASCII-IPA) ↔ IPA, and ARPABET → Kirshenbaum routing |
139
145
  | `13_script_runs.py` | Per-script segmentation of mixed-script text |
140
146
  | `14_kana_transliteration.py` | Hiragana ↔ Katakana |
147
+ | `15_cotovia.py` | Cotovía (Galician TTS notation) ↔ IPA |
141
148
 
142
149
  ## Fidelity guarantees
143
150
 
@@ -152,6 +159,7 @@ round-trip is exact and what happens to a symbol the table does not know.
152
159
  | **Buckwalter ↔ Arabic** | Exact (the `^` shadda alias normalises to canonical `~`; precomposed lam-alef ligatures decompose to two chars, visually identical) | Exact | Passed through unchanged |
153
160
  | **Lexique ↔ IPA** | Exact except the `°`/`3` schwa pair (both → `ə`; reverse always → `°`) | Exact | Passed through unchanged |
154
161
  | **Kirshenbaum ↔ IPA** | Exact | **Lossy** — restricted ASCII inventory; IPA outside it passes through | Passed through unchanged |
162
+ | **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 |
155
163
 
156
164
  This table is also available programmatically via `NOTATION_INFO`
157
165
  (`NotationInfo` records with `lossless_to_ipa`, `lossless_from_ipa`,
@@ -177,4 +185,5 @@ Derived tables used internally:
177
185
  | Buckwalter ↔ Arabic | Tim Buckwalter's Arabic transliteration scheme | — (factual 1:1 mapping) |
178
186
  | 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 |
179
187
  | Kirshenbaum ↔ IPA | Kirshenbaum 1993 ASCII-IPA standard (comp.speech), cross-checked against espeak-ng | — (factual symbol mapping) |
188
+ | Cotovía ↔ IPA | Universidade de Vigo GTM Cotovía TTS project (`fonemas.cpp`) | — (factual symbol mapping) |
180
189
  | Hangul jamo tables | [stannam/hangul_to_ipa](https://github.com/stannam/hangul_to_ipa) | — |
@@ -50,6 +50,9 @@ 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
+ looks_like_ipa,
53
56
  )
54
57
  from scriptconv.translit import (
55
58
  decompose_hangul,
@@ -86,6 +89,9 @@ __all__ = [
86
89
  "ipa_to_lexique",
87
90
  "kirshenbaum_to_ipa",
88
91
  "ipa_to_kirshenbaum",
92
+ "cotovia_to_ipa",
93
+ "ipa_to_cotovia",
94
+ "looks_like_ipa",
89
95
  # translit
90
96
  "decompose_hangul",
91
97
  "hira_to_kana",
@@ -37,6 +37,9 @@ __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
+ "looks_like_ipa",
40
43
  ]
41
44
 
42
45
 
@@ -50,6 +53,7 @@ class Notation(str, Enum):
50
53
  ARABIC = "arabic" # Arabic script (target/source for BW)
51
54
  LEXIQUE = "lexique" # Lexique one-char-per-phoneme French notation
52
55
  KIRSHENBAUM = "kirshenbaum" # ASCII-IPA (espeak-ng native notation)
56
+ COTOVIA = "cotovia" # Universidade de Vigo Cotovía TTS notation (gl)
53
57
 
54
58
  def __repr__(self) -> str:
55
59
  return f"Notation.{self.name}"
@@ -664,6 +668,124 @@ def ipa_to_kirshenbaum(ipa: str) -> str:
664
668
  return "".join(_IPA_TO_KIRSHENBAUM.get(ch, ch) for ch in ipa)
665
669
 
666
670
 
671
+ # ---------------------------------------------------------------------------
672
+ # Cotovía ↔ IPA
673
+ #
674
+ # Cotovía is the internal phoneme notation of the Universidade de Vigo GTM
675
+ # Cotovía TTS system (Galician/Spanish). The table is the phoneme symbol map
676
+ # from its ``fonemas.cpp``. Symbols are matched longest-first (``tS``, ``rr``
677
+ # before single characters). The silence/pause marker (``#``) is a boundary
678
+ # token, not a phoneme, and is intentionally excluded.
679
+ # ---------------------------------------------------------------------------
680
+
681
+ _COTOVIA_TO_IPA: dict[str, str] = {
682
+ # Double-character symbols first (longest-match ordering)
683
+ "tS": "tʃ",
684
+ "rr": "r", # trill (single "r" is the tap ɾ)
685
+ "jj": "ʎ",
686
+ # Consonants
687
+ "B": "β",
688
+ "D": "ð",
689
+ "G": "ɣ",
690
+ "J": "ɲ",
691
+ "N": "ŋ",
692
+ "S": "ʃ",
693
+ "T": "θ",
694
+ "Z": "ʎ",
695
+ "L": "ʎ", # canonical Cotovía symbol for the palatal lateral
696
+ "b": "b",
697
+ "d": "d",
698
+ "f": "f",
699
+ "g": "ɡ",
700
+ "k": "k",
701
+ "l": "l",
702
+ "m": "m",
703
+ "n": "n",
704
+ "p": "p",
705
+ "r": "ɾ", # tap
706
+ "s": "s",
707
+ "t": "t",
708
+ "x": "x",
709
+ "X": "x",
710
+ # Vowels
711
+ "E": "ɛ",
712
+ "O": "ɔ",
713
+ "a": "a",
714
+ "e": "e",
715
+ "i": "i",
716
+ "o": "o",
717
+ "u": "u",
718
+ # Glides
719
+ "j": "j",
720
+ "w": "w",
721
+ }
722
+
723
+ # Reverse: IPA → Cotovía. First-wins deduplicates the symbols that share an IPA
724
+ # value; pin ʎ → "L" (the standard Cotovía palatal-lateral symbol) and x → "x".
725
+ _IPA_TO_COTOVIA: dict[str, str] = {}
726
+ for _cv, _ip in _COTOVIA_TO_IPA.items():
727
+ _IPA_TO_COTOVIA.setdefault(_ip, _cv)
728
+ _IPA_TO_COTOVIA["ʎ"] = "L"
729
+ _IPA_TO_COTOVIA["x"] = "x"
730
+
731
+ _CV_KEYS_SORTED = sorted(_COTOVIA_TO_IPA.keys(), key=len, reverse=True)
732
+ _CV_RE = re.compile("|".join(re.escape(s) for s in _CV_KEYS_SORTED))
733
+ _IPA_CV_KEYS_SORTED = sorted(_IPA_TO_COTOVIA.keys(), key=len, reverse=True)
734
+ _IPA_CV_RE = re.compile("|".join(re.escape(s) for s in _IPA_CV_KEYS_SORTED))
735
+
736
+
737
+ def cotovia_to_ipa(cotovia: str) -> str:
738
+ """Convert a Cotovía phoneme string to IPA.
739
+
740
+ Multi-character symbols are matched longest-first. Characters outside the
741
+ table pass through unchanged.
742
+
743
+ Examples
744
+ --------
745
+ >>> cotovia_to_ipa("tS")
746
+ 'tʃ'
747
+ >>> cotovia_to_ipa("karro")
748
+ 'karo'
749
+ """
750
+ result = []
751
+ pos = 0
752
+ while pos < len(cotovia):
753
+ m = _CV_RE.match(cotovia, pos)
754
+ if m:
755
+ result.append(_COTOVIA_TO_IPA[m.group(0)])
756
+ pos = m.end()
757
+ else:
758
+ result.append(cotovia[pos])
759
+ pos += 1
760
+ return "".join(result)
761
+
762
+
763
+ def ipa_to_cotovia(ipa: str) -> str:
764
+ """Convert an IPA string to Cotovía notation.
765
+
766
+ Multi-character IPA symbols are matched longest-first. Characters outside
767
+ the table pass through unchanged.
768
+
769
+ Examples
770
+ --------
771
+ >>> ipa_to_cotovia("tʃ")
772
+ 'tS'
773
+ >>> ipa_to_cotovia("ʎ")
774
+ 'L'
775
+ """
776
+ result = []
777
+ pos = 0
778
+ while pos < len(ipa):
779
+ m = _IPA_CV_RE.match(ipa, pos)
780
+ if m:
781
+ result.append(_IPA_TO_COTOVIA[m.group(0)])
782
+ pos = m.end()
783
+ else:
784
+ result.append(ipa[pos])
785
+ pos += 1
786
+ return "".join(result)
787
+
788
+
667
789
  # ---------------------------------------------------------------------------
668
790
  # convert — facade routing through IPA where no direct map exists
669
791
  # ---------------------------------------------------------------------------
@@ -734,12 +856,14 @@ _TO_IPA: dict[Notation, "callable"] = {
734
856
  Notation.XSAMPA: xsampa_to_ipa,
735
857
  Notation.LEXIQUE: lexique_to_ipa,
736
858
  Notation.KIRSHENBAUM: kirshenbaum_to_ipa,
859
+ Notation.COTOVIA: cotovia_to_ipa,
737
860
  }
738
861
  _FROM_IPA: dict[Notation, "callable"] = {
739
862
  Notation.ARPA: ipa_to_arpa,
740
863
  Notation.XSAMPA: ipa_to_xsampa,
741
864
  Notation.LEXIQUE: ipa_to_lexique,
742
865
  Notation.KIRSHENBAUM: ipa_to_kirshenbaum,
866
+ Notation.COTOVIA: ipa_to_cotovia,
743
867
  }
744
868
  _DIRECT_PAIRS: dict[tuple[Notation, Notation], "callable"] = {
745
869
  (Notation.BUCKWALTER, Notation.ARABIC): buckwalter_to_arabic,
@@ -845,6 +969,11 @@ NOTATION_INFO: dict[Notation, NotationInfo] = {
845
969
  token_separated=False,
846
970
  reference="Kirshenbaum 1993 ASCII-IPA standard (comp.speech)",
847
971
  ),
972
+ Notation.COTOVIA: NotationInfo(
973
+ Notation.COTOVIA, lossless_to_ipa=False, lossless_from_ipa=False,
974
+ token_separated=False,
975
+ reference="Universidade de Vigo GTM Cotovía TTS (fonemas.cpp)",
976
+ ),
848
977
  Notation.BUCKWALTER: NotationInfo(
849
978
  Notation.BUCKWALTER, lossless_to_ipa=True, lossless_from_ipa=False,
850
979
  token_separated=False,
@@ -894,3 +1023,56 @@ def convert_batch(
894
1023
  yield stripped
895
1024
  else:
896
1025
  yield convert(stripped, _src, _dst)
1026
+
1027
+
1028
+ # ---------------------------------------------------------------------------
1029
+ # looks_like_ipa — heuristic notation detection
1030
+ # ---------------------------------------------------------------------------
1031
+
1032
+ # Unicode blocks/characters that are distinctive to IPA transcription. Basic
1033
+ # Latin letters (p, a, t, …) and the Greek letters IPA borrows (β, θ, χ) are
1034
+ # deliberately excluded: they are indistinguishable from ordinary Latin/Greek
1035
+ # text, so they carry no signal.
1036
+ _IPA_DISTINCTIVE_RANGES = (
1037
+ (0x0250, 0x02AF), # IPA Extensions
1038
+ (0x02B0, 0x02FF), # Spacing Modifier Letters (ˈ ˌ ː ˑ ʰ ʲ …)
1039
+ (0x0363, 0x036F), # Combining diacritics used in IPA
1040
+ (0x1D00, 0x1D7F), # Phonetic Extensions
1041
+ (0x1D80, 0x1DBF), # Phonetic Extensions Supplement
1042
+ )
1043
+ # Individual combining marks common in IPA (nasalisation, syllabicity, etc.).
1044
+ _IPA_DISTINCTIVE_CHARS = frozenset("̩̪̥̃̊͜͡")
1045
+
1046
+
1047
+ def _is_ipa_distinctive(ch: str) -> bool:
1048
+ cp = ord(ch)
1049
+ if ch in _IPA_DISTINCTIVE_CHARS:
1050
+ return True
1051
+ return any(lo <= cp <= hi for lo, hi in _IPA_DISTINCTIVE_RANGES)
1052
+
1053
+
1054
+ def looks_like_ipa(text: str) -> bool:
1055
+ """Heuristically decide whether *text* is an IPA transcription.
1056
+
1057
+ Returns ``True`` when *text* contains at least one character that is
1058
+ distinctive to IPA — from the IPA Extensions, Spacing Modifier Letters,
1059
+ or Phonetic Extensions blocks, or an IPA combining diacritic (e.g.
1060
+ ``ɑ``, ``ʃ``, ``ː``, ``ˈ``, nasalisation).
1061
+
1062
+ This is a positive-signal heuristic, not a classifier: a transcription
1063
+ written only with characters IPA shares with the Latin alphabet
1064
+ (``"pat"``, ``"bad"``) has no distinctive marker and returns ``False``.
1065
+ It cannot be otherwise — those characters are the same codepoints as
1066
+ ordinary text. Use it to *guard* against feeding IPA where orthography
1067
+ is expected, not to prove a string is not IPA.
1068
+
1069
+ Examples
1070
+ --------
1071
+ >>> looks_like_ipa("pʰɑtʃ")
1072
+ True
1073
+ >>> looks_like_ipa("ˈhɛloʊ")
1074
+ True
1075
+ >>> looks_like_ipa("hello")
1076
+ False
1077
+ """
1078
+ return any(_is_ipa_distinctive(ch) for ch in text)
@@ -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 = 5
4
+ VERSION_BUILD = 2
5
+ VERSION_ALPHA = 1
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.1a5
3
+ Version: 0.0.2a1
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,
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,7 @@ 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,
78
+ kirshenbaum_to_ipa, ipa_to_kirshenbaum, cotovia_to_ipa, ipa_to_cotovia, looks_like_ipa,
79
79
  decompose_hangul, hira_to_kana, kana_to_hira,
80
80
  convert, can_convert, convert_batch, Notation, NOTATION_INFO,
81
81
  )
@@ -107,6 +107,12 @@ ipa_to_lexique("vɛ̃") # "v5" (vin)
107
107
  kirshenbaum_to_ipa("S") # "ʃ" (espeak-ng ASCII-IPA)
108
108
  ipa_to_kirshenbaum("ŋ") # "N"
109
109
 
110
+ cotovia_to_ipa("karro") # "karo" (Galician Cotovía TTS notation)
111
+ ipa_to_cotovia("ʎ") # "L"
112
+
113
+ looks_like_ipa("pʰɑtʃ") # True (heuristic: has IPA-distinctive symbols)
114
+ looks_like_ipa("hello") # False
115
+
110
116
  decompose_hangul("국민") # "ㄱㅜㄱㅁㅣㄴ" (orthographic jamo, no assimilation)
111
117
  hira_to_kana("ひらがな") # "ヒラガナ"
112
118
  kana_to_hira("カタカナ") # "かたかな"
@@ -136,7 +142,7 @@ python -m scriptconv lang ko
136
142
  | Module | Contents |
137
143
  |--------|----------|
138
144
  | `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` |
139
- | `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) |
145
+ | `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), `looks_like_ipa` detector |
140
146
  | `scriptconv.translit` | `decompose_hangul` (Hangul blocks → jamo, compatibility or conjoining), `hira_to_kana`/`kana_to_hira` — all orthographic only |
141
147
 
142
148
  ## Documentation
@@ -165,6 +171,7 @@ Runnable scripts in [examples/](examples/):
165
171
  | `12_kirshenbaum.py` | Kirshenbaum (ASCII-IPA) ↔ IPA, and ARPABET → Kirshenbaum routing |
166
172
  | `13_script_runs.py` | Per-script segmentation of mixed-script text |
167
173
  | `14_kana_transliteration.py` | Hiragana ↔ Katakana |
174
+ | `15_cotovia.py` | Cotovía (Galician TTS notation) ↔ IPA |
168
175
 
169
176
  ## Fidelity guarantees
170
177
 
@@ -179,6 +186,7 @@ round-trip is exact and what happens to a symbol the table does not know.
179
186
  | **Buckwalter ↔ Arabic** | Exact (the `^` shadda alias normalises to canonical `~`; precomposed lam-alef ligatures decompose to two chars, visually identical) | Exact | Passed through unchanged |
180
187
  | **Lexique ↔ IPA** | Exact except the `°`/`3` schwa pair (both → `ə`; reverse always → `°`) | Exact | Passed through unchanged |
181
188
  | **Kirshenbaum ↔ IPA** | Exact | **Lossy** — restricted ASCII inventory; IPA outside it passes through | Passed through unchanged |
189
+ | **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 |
182
190
 
183
191
  This table is also available programmatically via `NOTATION_INFO`
184
192
  (`NotationInfo` records with `lossless_to_ipa`, `lossless_from_ipa`,
@@ -204,4 +212,5 @@ Derived tables used internally:
204
212
  | Buckwalter ↔ Arabic | Tim Buckwalter's Arabic transliteration scheme | — (factual 1:1 mapping) |
205
213
  | 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 |
206
214
  | Kirshenbaum ↔ IPA | Kirshenbaum 1993 ASCII-IPA standard (comp.speech), cross-checked against espeak-ng | — (factual symbol mapping) |
215
+ | Cotovía ↔ IPA | Universidade de Vigo GTM Cotovía TTS project (`fonemas.cpp`) | — (factual symbol mapping) |
207
216
  | Hangul jamo tables | [stannam/hangul_to_ipa](https://github.com/stannam/hangul_to_ipa) | — |
@@ -105,3 +105,9 @@ 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()
@@ -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) == 8
951
951
 
952
952
 
953
953
  def test_notation_str():
@@ -1027,3 +1027,84 @@ def test_lossless_to_ipa_flag_backed_by_round_trip():
1027
1027
  if info.lossless_to_ipa and notation in samples:
1028
1028
  for ch in samples[notation]:
1029
1029
  assert from_ipa[notation](to_ipa[notation](ch)) == ch, notation
1030
+
1031
+
1032
+ # ---------------------------------------------------------------------------
1033
+ # looks_like_ipa — heuristic notation detection
1034
+ # ---------------------------------------------------------------------------
1035
+
1036
+ from scriptconv.notation import looks_like_ipa # noqa: E402
1037
+
1038
+
1039
+ @pytest.mark.parametrize("text", [
1040
+ "pʰɑtʃ", # IPA Extensions + modifier
1041
+ "ˈhɛloʊ", # stress mark + IPA Extensions
1042
+ "ɑ", # single IPA Extensions char
1043
+ "næ̃", # combining nasalisation
1044
+ "kat̪", # combining dental diacritic
1045
+ ])
1046
+ def test_looks_like_ipa_true(text):
1047
+ assert looks_like_ipa(text) is True
1048
+
1049
+
1050
+ @pytest.mark.parametrize("text", [
1051
+ "", # empty
1052
+ "hello", # plain Latin — ambiguous, no distinctive marker
1053
+ "att", # overlaps Latin
1054
+ "café", # accented Latin is not IPA-distinctive
1055
+ "θaβ", # bare Greek letters carry no IPA signal
1056
+ "مرحبا", # Arabic
1057
+ ])
1058
+ def test_looks_like_ipa_false(text):
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"
File without changes
File without changes