Features
- Beatmap Parsing - Full
.osufile parsing including all sections: General, Metadata, Difficulty, Events, Timing Points, Hit Objects, Colors, and Editor - Mod Handling - Complete mod system supporting all osu! game modes (osu!, Taiko, Catch, Mania) with legacy and modern mod representations
- Star Rating & PP Calculation - Complete difficulty and performance calculation for all four rulesets (osu!, osu!taiko, osu!catch, osu!mania)
- Up to date - Implements the 2026 Q2 pp/star rating rework (osu! deploy
2026.702.1, July 2026): the new osu! Reading skill, Snap/Flow aim, deviation-based speed pp, reworked taiko rhythm, catch linear-spacing nerf - Bit-exact - Verified bit-for-bit identical to the official osu! C# implementation across a 4,400+ case test matrix (all rulesets, all mod combinations, converts, lazer & stable scores)
- Converts - Faithful osu! → taiko / catch / mania conversion, including the legacy mania pattern generator with osu!stable RNG and key mods (1K-9K)
- Lazer & Stable scores - Both scoring systems supported: lazer slider statistics (slider tail hits, large ticks) as well as classic scores incl. score-based miss estimation from
legacy_total_score - Type-safe - Fully typed codebase, mypy-checked
Installation
pip install parsecore
Or with uv:
uv add parsecore
Requires Python 3.10+
Quick Start
Beatmap Parsing
from parsecore.Beatmap import Beatmap
beatmap = Beatmap.from_path("path/to/map.osu")
print(beatmap.metadata.title)
print(beatmap.metadata.version)
print(beatmap.difficulty.approach_rate)
print(beatmap.difficulty.circle_size)
for obj in beatmap.hit_objects.hit_objects:
print(obj)
for tp in beatmap.timing_points.control_points.timing_points:
print(tp.time, 60000 / tp.beat_len) # start time, BPM
Star Rating (Difficulty)
from parsecore.Performance import Beatmap, Difficulty
bm = Beatmap.from_path("path/to/map.osu")
# NoMod
attrs = Difficulty().calculate(bm)
print(attrs.stars, attrs.max_combo)
# Mods via legacy bitflags (HD = 8, DT = 64, ...)
attrs = Difficulty().mods(8 | 64).calculate(bm)
print(attrs.stars)
# osu! attributes include the per-skill breakdown of the 2026 rework
print(attrs.aim, attrs.speed, attrs.reading, attrs.flashlight)
# Custom clock rate, difficulty overrides, partial plays
attrs = (
Difficulty()
.mods(64)
.clock_rate(1.3)
.ar(10, fixed=True) # fixed=True: use as-is; fixed=False: mods still apply
.passed_objects(500) # difficulty of the first 500 objects only
.calculate(bm)
)
Performance Points (PP)
from parsecore.Performance import Beatmap, Performance
bm = Beatmap.from_path("path/to/map.osu")
# From accuracy (the score state is generated automatically)
result = Performance(bm).mods(64).accuracy(98.5).misses(2).calculate()
print(result.pp)
# From an explicit score state (osu!lazer score)
result = (
Performance(bm)
.lazer(True)
.n300(194).n100(0).n50(0).misses(0)
.combo(277)
.slider_end_hits(68)
.large_tick_hits(15)
.calculate()
)
print(result.pp, result.pp_aim, result.pp_speed, result.pp_acc, result.pp_reading)
# osu!(stable) / classic score, incl. score-based miss estimation
result = (
Performance(bm)
.lazer(False)
.n300(2020).n100(27).misses(4)
.combo(1699)
.legacy_total_score(31_546_804)
.calculate()
)
print(result.pp, result.effective_miss_count)
Converts (osu! → taiko / catch / mania)
from parsecore.Beatmap.beatmap import Beatmap as UserBeatmap
from parsecore.Performance import Beatmap, Difficulty, GameMode
user_map = UserBeatmap.from_path("path/to/osu_map.osu")
# Play an osu! map in another ruleset
bm = Beatmap.from_user_beatmap(user_map, override_mode=GameMode.MANIA)
attrs = Difficulty().calculate(bm)
print(attrs.stars, attrs.n_objects, attrs.n_hold_notes)
# mania key mods change the column count of converts (7K = 1 << 18)
attrs = Difficulty().mods(1 << 18).calculate(bm)
Mod Handling
from parsecore.Mods import GameMods, GameMode
# From an acronym string
mods = GameMods.from_acronyms("HDDT", GameMode.Osu)
print(mods) # DTHD
print(mods.clock_rate()) # 1.5
# Legacy bitfield conversion
legacy = mods.as_legacy()
print(legacy.bits()) # 72
# Intermode mods (not bound to a specific ruleset)
from parsecore.Mods import GameModsIntermode
intermode = GameModsIntermode.from_acronyms(["HD", "NC"])
print(intermode) # HDNC
How the pp calculation works
parsecore.Performance is a pure-Python port of the official osu! difficulty and
performance algorithms (ppy/osu, deploy 2026.702.1) not of a third-party
reimplementation. The pipeline:
- Parse - the
.osufile is decoded into hit objects, timing/difficulty/effect points with osu!-faithful float semantics (positions and curve math are computed in 32-bit floats exactly like the game client). - Convert - if the target ruleset differs from the map's native mode, the map is converted first (taiko drum-roll splitting with scroll-speed effect points, catch mode flag, mania legacy pattern generator with osu!stable RNG).
- Preprocess - per-ruleset difficulty objects are built (distances, angles, rhythm groupings, effective BPM, ...).
- Skills - each ruleset evaluates its skills (osu!: Aim, Speed, Reading, Flashlight; taiko: Rhythm, Reading, Colour, Stamina; catch: Movement; mania: Strain) and aggregates them into the star rating.
- Performance - the score state (either given explicitly or generated from accuracy/miss count) is combined with the difficulty attributes into pp, including miss penalties, slider-break estimation and the classic/lazer scoring differences.
Every step reproduces the C# reference including its floating-point quirks (f32 intermediates, IEEE division semantics, integer-exponent powers as explicit multiplication, C# sorting algorithms, legacy RNG), which is what makes the results bit-identical rather than merely close.
Verification
Correctness is enforced by a parity test suite that compares parsecore against an
oracle built from the official ppy.osu.Game packages:
- 4,400+ cases: star ratings and pp across all four rulesets
- extended mod matrix (EZ/HR/DT/NC/HT/HD/FL/TD/RX/AP/SO, key mods 1K-9K, combinations)
- all convert directions, native maps, edge-case and pathological maps
- randomized full score states, partial states, fails, lazer & stable scores
- result: 0 differences every value bit-identical to the official implementation
Note: After a pp rework deploys, public calculators and bots that rely on outdated libraries can disagree with parsecore. When in doubt: new scores set in-game receive exactly the values parsecore computes.
Project Structure
parsecore/
├── Beatmap/ # .osu file parsing and encoding
│ ├── beatmap.py
│ ├── reader.py
│ ├── encode.py
│ └── section/ # Individual section parsers
│ ├── general.py
│ ├── metadata.py
│ ├── difficulty.py
│ ├── timing_points.py
│ ├── hit_objects/
│ └── ...
├── Mods/ # Mod system
│ ├── game_mod.py
│ ├── game_mods.py
│ ├── game_mode.py
│ ├── generated_mods.py
│ └── ...
└── Performance/ # Star rating & pp calculation
├── api.py # Public API: Beatmap, Difficulty, Performance
├── utils.py # Rust/C#-faithful float & RNG helpers
├── data/ # Beatmap model, mods, score state, attributes
└── rulesets/
├── osu/ # Aim, Speed, Reading, Flashlight + pp
├── taiko/ # Rhythm, Reading, Colour, Stamina + pp
├── catch/ # Movement + pp, gradual calculation
└── mania/ # Strain + pp, legacy convert pattern generator
Contributing
Contributions are welcome! Please read CONTRIBUTING.md before submitting a pull request.
Security
To report a security vulnerability, see SECURITY.md.
Translations
parsecore's documentation is translated by the community on Crowdin. Want to see it in your language? Join the project and help translate no coding required.
Currently supported languages
| Language | |
|---|---|
| 🇺🇸 English (US) | Source |
| 🇩🇪 German | Translate → |
| 🇫🇷 French | Translate → |
| 🇱🇺 Luxembourgish | Translate → |
| 🇵🇹 Portuguese | Translate → |
The live translated site is on Read the Docs. Want to help? Join the project on Crowdin no coding required.
© 2026-Present O!Lib Team
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file parsecore-1.0.1.tar.gz.
File metadata
- Download URL: parsecore-1.0.1.tar.gz
- Upload date:
- Size: 165.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f2d36ce28f77326b134d1ad6ff995da319f4542ed99c8a4a0fb74a1378e3b188
|
|
| MD5 |
3fd350e7fbf26deea10aad0d7a26baed
|
|
| BLAKE2b-256 |
89b43699efd7d52d0b44f7228643ca1da6784b2d9fb7def6f5a1a1627480bd19
|
Provenance
The following attestation bundles were made for parsecore-1.0.1.tar.gz:
Publisher:
publish.yml on O-Lib/parsecore
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
parsecore-1.0.1.tar.gz -
Subject digest:
f2d36ce28f77326b134d1ad6ff995da319f4542ed99c8a4a0fb74a1378e3b188 - Sigstore transparency entry: 2161424296
- Sigstore integration time:
-
Permalink:
O-Lib/parsecore@454d928e7622667c8773380775216a8c96997bd1 -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/O-Lib
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@454d928e7622667c8773380775216a8c96997bd1 -
Trigger Event:
release
-
Statement type:
File details
Details for the file parsecore-1.0.1-py3-none-any.whl.
File metadata
- Download URL: parsecore-1.0.1-py3-none-any.whl
- Upload date:
- Size: 240.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2c0b0e47fb6653269e5a6eb3011229dde3b33695139cc8ae6c0eed32acce416f
|
|
| MD5 |
74e46c06c739432d15061bf6bec41c0c
|
|
| BLAKE2b-256 |
71e1909c94f1d1446c039833f657a19f60dd29eacebd1c9cf5ff8f7b09d09c7c
|
Provenance
The following attestation bundles were made for parsecore-1.0.1-py3-none-any.whl:
Publisher:
publish.yml on O-Lib/parsecore
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
parsecore-1.0.1-py3-none-any.whl -
Subject digest:
2c0b0e47fb6653269e5a6eb3011229dde3b33695139cc8ae6c0eed32acce416f - Sigstore transparency entry: 2161424440
- Sigstore integration time:
-
Permalink:
O-Lib/parsecore@454d928e7622667c8773380775216a8c96997bd1 -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/O-Lib
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@454d928e7622667c8773380775216a8c96997bd1 -
Trigger Event:
release
-
Statement type: