Metadata-Version: 2.4
Name: syllable-counter
Version: 0.2.2
Summary: Multi-language syllable counter and TTS duration estimator (12 languages)
Author: primepake
License: MIT
Keywords: syllable,tts,duration,linguistics,multilingual
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Topic :: Text Processing :: Linguistic
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pyphen>=0.14
Requires-Dist: pypinyin>=0.50
Requires-Dist: numpy>=1.20
Requires-Dist: num2words>=0.5
Provides-Extra: ja
Requires-Dist: pyopenjtalk; extra == "ja"
Provides-Extra: he
Requires-Dist: phonikud; extra == "he"
Provides-Extra: all
Requires-Dist: pyopenjtalk; extra == "all"
Requires-Dist: phonikud; extra == "all"
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Dynamic: license-file

# syllable-counter

Multi-language syllable counter and TTS duration estimator. Supports **12 languages** with per-language strategies (pyphen hyphenation, pypinyin, regex on Hangul blocks, optional pyopenjtalk/phonikud).

## Install

```bash
pip install syllable-counter
```

Optional native backends for better accuracy:

```bash
pip install syllable-counter[ja]   # adds pyopenjtalk for Japanese
pip install syllable-counter[he]   # adds phonikud for Hebrew
pip install syllable-counter[all]  # both
```

## Usage

```python
from syllable_counter import count_syllables, estimate_duration

# Count syllables
count_syllables("hello world", lang="en")       # 3
count_syllables("xin chào các bạn", lang="vi")  # 4
count_syllables("我是中国人", lang="zh")          # 5
count_syllables("iPhone15", lang="en")           # iPhone + 15 = ~6

# Numbers are read in the target language
count_syllables("2026", lang="en")  # "two thousand and twenty-six"  → 7
count_syllables("2026", lang="es")  # "dos mil veintiséis"           → 4
count_syllables("2026", lang="zh")  # "二零二六"                       → 4

# Symbols and units are spelled out
count_syllables("$25",   lang="en")  # "twenty-five dollars"       → ~5
count_syllables("25°C",  lang="en")  # "twenty-five degrees celsius" → ~7
count_syllables("5km",   lang="en")  # "five kilometers"           → ~4
count_syllables("100MB", lang="en")  # "one hundred megabytes"     → ~6
count_syllables("50%",   lang="fr")  # "cinquante pour cent"       → ~5

# Estimate TTS duration
est = estimate_duration("hello world", lang="en")
# DurationEstimate(seconds=0.87, frames=47, syllable_count=3, ...)
```

## Supported languages

| Code | Language | Backend |
|------|----------|---------|
| `en` | English | pyphen + vowel-group fallback |
| `es` | Spanish | pyphen + vowel-group fallback |
| `fr` | French | pyphen + vowel-group fallback |
| `de` | German | pyphen + vowel-group fallback |
| `it` | Italian | pyphen + vowel-group fallback |
| `pt` | Portuguese | pyphen + vowel-group fallback |
| `ru` | Russian | pyphen + per-vowel Cyrillic fallback |
| `vi` | Vietnamese | whitespace tokens (monosyllabic) |
| `zh` | Chinese | pypinyin |
| `ja` | Japanese | pyopenjtalk (optional) → char-range fallback |
| `ko` | Korean | regex on Hangul blocks |
| `he` | Hebrew | phonikud (optional) → CV-structure fallback |

Numbers are handled per-language via `num2words` (45 languages natively, with English fallback for the rest, plus digit-by-digit native fallback for Chinese).

## Calibrated duration estimation

When you have reference word timestamps from a target speaker, you can calibrate:

```python
ref_words = [
    ("hello", 0.0, 0.4),
    ("world", 0.5, 0.9),
    ("how", 1.0, 1.2),
    ("are", 1.3, 1.5),
    ("you", 1.6, 1.8),
]
est = estimate_duration("nice to meet you", lang="en", ref_words=ref_words)
est.calibrated           # True
est.sec_per_syllable_used  # fitted from the reference
```

Calibration uses OLS to fit `word_duration ≈ silence + sec_per_syl × n_syllables`. Falls back to per-language defaults when fewer than 5 usable reference words are provided.

## Pause-aware

`count_pauses(text)` returns `(short, medium, long)` punctuation counts:

```python
count_pauses("hello, world. yes!")  # (1, 0, 2)
```

`estimate_duration` automatically adds pause time:
- short (`,`, `、`, `，`) = 1× short_pause
- medium (`;`, `:`, `；`, `：`) = 1.5× short_pause
- long (`.`, `!`, `?`, `。`, `！`, `？`) = 2.5× short_pause

## API

```python
count_syllables(text: str, lang: str) -> int
compute_sec_per_syllable(ref_words, lang) -> (sec_per_syl, silence_overhead)
compute_short_pause(ref_words) -> float
count_pauses(text: str) -> (short, medium, long)
estimate_duration(target_text, lang, ref_words=None, ...) -> DurationEstimate
```

Errors:
- `UnsupportedLanguageError` — `lang` not in `SUPPORTED_LANGS`
- `InsufficientReferenceError` — calibration needs ≥5 usable words

## Limitations

- Pyphen-based fallback for unknown words is English-tuned for silent-e
- `'5.5'` reads as "five five" (decimal point is lost; off by 1 syl)
- `'-5'` reads as just "five" (negative sign lost; off by 2 syl)
- Hebrew without nikud uses `ceil(letters/2)` approximation (±1 syl per word)
- Japanese without `pyopenjtalk` treats each kanji as 1 mora (kanji can be 2–3)

## License

MIT
