Skip to main content

Polish Whisper Normalizer

CI PyPI Python License Docs

Polish port of OpenAI Whisper normalizers — preserves ąćęłńóśźż, normalizes numbers, time, currency, dates and declensions via Morfeusz2.

English | Polski


English

Features

Area Example Normalized
Diacritics Żółć! żółć (kept) / zolc with remove_diacritics=True
Cardinal / ordinal sto dwadzieścia trzy, dwudziestu pięciu, pierwszego → 1 123, 25, 1 (ordinal dots dropped)
Numeral separation 10, 500, 90 vs dwa tysiące dwadzieścia trzy 10 500 90 vs 2023 (punctuation/composition aware)
Time piąta trzydzieści, wpół do ósmej, o piątej, piąta rano, od piątej do szóstej 5:30, 7:30, o 5:00, 5:00 rano, od 5:00 do 6:00
Time with minut dziesięć minut po piątej, za dwadzieścia minut ósma 5:10, 7:40
Fractions jedna trzecia, trzy czwarte 1/3, 3/4 (keeps /, idempotent)
Half pół litra, półtora, dwa i pół 0.5 litra, 1.5, 2.5
Currency pięć złotych, €10, pięć złotówek 5 zł, 10 €, 5 zł
Percent pięć procentów 5%
Ordinal multipliers tysiąc dziewięćsetny 1900
Dates 5 maja → 05.05 · piątego maja → 05.05 · piątego maja 2026 → 05.05.2026 · piątego maja roku dwa tysiące dwudziestego szóstego → 05.05.2026 DD.MM / DD.MM.YYYY zero-padded, conditional (maja alone stays maja, avoids Maja→5)
Geographic guard na północ stays, jest północ → jest 0:00 no false 0:00 for north

Pipeline (text.py): lower → brackets/parens/ignore → r.→roku → sentence periods/ellipsis → time (Morfeusz) → decimal ,→. → numeral boundaries → remove_symbols(keep=".:/%$€£¢+-") → numbers (Morfeusz) → months (conditional, Morfeusz) → dates → ordinal-dot cleanup. Configurable via PolishTextNormalizer(date_format=...). Diacritic-less ASR (czterdziesci, piec, wpol) handled via utils + Morfeusz.generate.

Installation

uv sync                 # dev + morfeusz2
uv pip install -e .     # editable
# or from PyPI
uv pip install polish-whisper-normalizer
# with jiwer extra
uv pip install "polish-whisper-normalizer[jiwer]"

Requires Python >=3.10, regex, morfeusz2.

Quickstart

from polish_whisper_normalizer import PolishTextNormalizer, BasicTextNormalizer

n = PolishTextNormalizer()
n("Było piętnaście po piątej, minus dziesięć stopni.")
# → "było 5:15 -10 stopni"

n("Spotkanie dwudziestego pierwszego maja o piętnastej trzydzieści.")
# → "spotkanie 21.05 o 15:30"

n("piątego maja roku dwa tysiące dwudziestego szóstego")
# → "05.05.2026"   # uniform, no trailing "r"

n("5 maja")  # digit day works too
# → "05.05"

n("pięć złotówek, pięć procentów, pół litra, jedna trzecia")
# → "5 zł 5% 0.5 litra 1/3"

# custom date format
PolishTextNormalizer(date_format="%Y-%m-%d")("piątego maja 2026")
# → "2026-05-05"
PolishTextNormalizer(date_format="{day}/{month}/{year}")("piątego maja 2026")
# → "5/5/2026"

# diacritics
BasicTextNormalizer()("Żółć!")  # → "żółć"
BasicTextNormalizer(remove_diacritics=True)("Żółć!")  # → "zolc"
# diacritic-less numbers also work
PolishTextNormalizer()("trzysta czterdziesci osiem")  # → "348"

Jiwer — WER with Polish normalization

uv pip install "polish-whisper-normalizer[jiwer]"
# or
uv sync --extra jiwer

Without normalization jiwer.wer penalizes piątego maja 2026 vs 05.05.2026 as 100% error. With PolishTransform they match:

import jiwer
from polish_whisper_normalizer.jiwer import wer, PolishTransform, polish_transform

# helper (recommended) — normalizes both sides with PolishTextNormalizer
wer("piątego maja 2026", "05.05.2026")  # → 0.0
wer("o piątej", "o 5:00")  # → 0.0
wer("pięć złotówek", "5 zł")  # → 0.0

# raw jiwer would be 1.0
jiwer.wer("piątego maja 2026", "05.05.2026")  # → 1.0

# via jiwer transforms (for pipelines)
jiwer.wer("piątego maja 2026", "05.05.2026",
          reference_transform=polish_transform,
          hypothesis_transform=polish_transform)  # → 0.0

# custom date_format is forwarded
wer("piątego maja 2026", "2026-05-05", date_format="%Y-%m-%d")  # → 0.0

# manual Compose
tr = jiwer.Compose([PolishTransform(date_format="%Y-%m-%d"), jiwer.RemoveMultipleSpaces(), jiwer.Strip(), jiwer.ReduceToListOfListOfWords()])
jiwer.wer("piątego maja 2026", "2026-05-05",
          reference_transform=tr, hypothesis_transform=tr)  # → 0.0

PolishTransform wraps PolishTextNormalizer (date_format kwarg supported) and returns str; polish_transform is the ready Compose ending with ReduceToListOfListOfWords required by jiwer.wer.

Architecture

src/polish_whisper_normalizer/
  basic.py        # Whisper basic normalizer, diacritics-aware
  lemmatizer.py   # PolishLemmatizer – thin Morfeusz2 wrapper (analyse/generate)
  utils.py        # strip_diacritics, with_ascii_variants – single place for ASCII fallback
  numbers.py      # PolishNumberNormalizer – cardinals/ordinals/currency/percent
  time.py         # PolishTimeNormalizer – HH:MM, wpół/za/po, geographic guard
  text.py         # PolishTextNormalizer – full pipeline: time → numbers → months → dates
  polish.py       # compatibility re-export shim
  jiwer.py        # PolishTransform / wer helper

Declension is never re-implemented: base nominative lexicons are stored, all declined forms are resolved via PolishLemmatizer.analyse and generate (Morfeusz2). ASCII-folded variants are derived once via utils and Morfeusz.generate, so diacritic-less ASR needs no duplicated dictionaries.

Components
from polish_whisper_normalizer import PolishNumberNormalizer, PolishTimeNormalizer, PolishLemmatizer

PolishNumberNormalizer()("dwudziestu pięciu złotych")  # → "25 zł"
PolishTimeNormalizer()("wpół do ósmej")  # → "7:30"
PolishLemmatizer().analyse("dwudziestu")  # → [("dwadzieścia","num")]
Class Description
PolishTextNormalizer(date_format="{day:02d}.{month:02d}.{year}") Full pipeline, date_format supports str.format ({day}, {month}, {year}) and strftime (%d.%m.%Y)
PolishNumberNormalizer Words → digits, currency/percent/decimals/signs/ordinals
PolishTimeNormalizer Spoken time → HH:MM, guards o 5. stronie vs o 5:00, na północ vs 0:00
PolishLemmatizer Morfeusz2 wrapper
BasicTextNormalizer Whisper basic but diacritics-aware

API Docs

Full API: neonfeline.github.io/polish-whisper-normalizer (mkdocs serve locally).

Development

uv sync --group dev
uv run pytest -q          # 600+ tests
uv run mypy src           # strict, py.typed
uv run ruff check src tests && uv run ruff format --check src tests
uv build
mkdocs serve              # strict
  • py.typed + mypy --strict (warn_unused_ignores=false)
  • ruff + pre-commit + GitHub Actions (ci.yml: lint → mypy → pytest --cov → build)
  • Validated on BIGOS v2 + PELCRA (2397 samples, ~5% number words)

License

MIT — see LICENSE (inherits Whisper MIT for basic.py).


Polski

Funkcje

Obszar Przykład Po normalizacji
Znaki diakrytyczne Żółć! żółć (zachowane)
Liczebniki sto dwadzieścia trzy, pierwszego 123, 1.
Czas piąta trzydzieści, o piątej, od piątej do szóstej 5:30, o 5:00, od 5:00 do 6:00
Daty 5 maja → 05.05 · piątego maja 2026 → 05.05.2026 DD.MM / DD.MM.RRRR, warunkowo (maja samo → maja)
Waluta / procent / ułamki pięć złotówek, procentów, 1/3, pół litra 5 zł, 5%, 1/3, 0.5 litra

Potok: lower → czas → liczby → miesiące (warunkowo) → daty.

Instalacja i użycie (PL)

uv sync                 # dev + morfeusz2
uv pip install polish-whisper-normalizer  # z PyPI
uv pip install "polish-whisper-normalizer[jiwer]"  # z jiwer
from polish_whisper_normalizer import PolishTextNormalizer
n = PolishTextNormalizer()  # date_format="{day:02d}.{month:02d}.{year}" domyślnie

n("piątego maja roku dwa tysiące dwudziestego szóstego")
# → "05.05.2026"  # jednolicie, bez "r"

n("5 maja")  # też działa
# → "05.05"

# własny format daty
PolishTextNormalizer(date_format="%Y-%m-%d")("piątego maja 2026")
# → "2026-05-05"

# bez znaków diakrytycznych też działa
n("trzysta czterdziesci osiem")
# → "348"

maja jako imię Maja zostaje maja (nie 5), na północ nie staje się 0:00.

Jiwer — WER po polsku

uv pip install "polish-whisper-normalizer[jiwer]"
from polish_whisper_normalizer.jiwer import wer
wer("piątego maja 2026", "05.05.2026")  # → 0.0  (bez normalizacji 1.0)
wer("o piątej", "o 5:00")  # → 0.0

Rozwój / testy

Jak wyżej — uv run pytest, mypy, ruff, mkdocs serve.


Made for BIGOS / Whisper WER — PRs welcome!

Metadata

Release files for polish-whisper-normalizer 0.1.17

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for polish-whisper-normalizer 0.1.17
File Size Uploaded
polish_whisper_normalizer-0.1.17.tar.gz 32.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for polish-whisper-normalizer 0.1.17
File Interpreter ABI Platform
polish_whisper_normalizer-0.1.17-py3-none-any.whl Python 3 none any Details

Total release size: 69.0 kB

Release files / polish_whisper_normalizer-0.1.17.tar.gz

Download URL polish_whisper_normalizer-0.1.17.tar.gz
Size 32.6 kB
Tags Source
SHA-256 checksum
How to use checksums
4adf4ac0232b5c7d727bef6e22f5a819c5650d89e150ca2b64dea821f73eb27d
BLAKE2b-256 checksum
How to use checksums
650e1476c3cfde0520b32a36df40324af1401b6e3a12128bc01e0fcd2669de6e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 28, 2026.

Transparency log

Release files / polish_whisper_normalizer-0.1.17-py3-none-any.whl

Download URL polish_whisper_normalizer-0.1.17-py3-none-any.whl
Size 36.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d93011ff2843a1635b1a14847d571b19358aa6cd1b54d98f1e0d1b469abd4f05
BLAKE2b-256 checksum
How to use checksums
c6fe32fe40cf76906359963207702eb3c29885f0d53c5f347c22cbfec265698a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 28, 2026.

Transparency log

Release history Release notifications | RSS feed

0.1.18

2 release files

This release

0.1.17 This release

2 release files

0.1.13

2 release files

0.1.12

2 release files

0.1.9

2 release files

0.1.4

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page