A Python library for parsing and analyzing Beat Saber BSOR replay files.
Project description
replayed - Beat Saber Replay Parser
Version: 0.1.0
replayed is a Python library designed for parsing, analyzing, and manipulating Beat Saber replay files (.bsor format). It leverages Pydantic 2.0 for robust data modeling, validation, and serialization, ensuring type safety and ease of use. The library provides a structured way to access all data points within a replay file, from player information and song details to intricate frame-by-frame tracking data and note events.
Features
- Pydantic-Powered Models: Clean, validated, and type-hinted data models for all aspects of a BSOR replay file.
- Comprehensive Parsing: Reads BSOR files (up to version 1, with warnings for newer versions) into Python objects.
- Binary Serialization: Writes modified or newly created replay data back into the BSOR binary format.
- Modular Design: Code is organized into logical modules for models, I/O utilities, constants, and base types.
- User-Friendly Interface: High-level functions for common tasks like loading and saving replays.
- Detailed Data Access: Access to player info, song metadata, frame-by-frame HMD and controller tracking, note cut events, wall interactions, pauses, and more.
- Customizable: Pydantic models can be extended for custom data or analyses.
- Error Handling: Custom
BSExceptionfor replay-specific errors.
Project Structure
The replayed module is organized as follows:
replayed/
├── __init__.py # Main module entry point, exports key classes and functions.
├── base_types.py # Base classes like `PydanticWritableBase` and `BSException`.
├── constants.py # Game and BSOR format constants (note types, magic numbers, etc.).
├── io_utils.py # Low-level functions for encoding/decoding binary data types.
├── utils.py # General utility functions (e.g., `clamp`, `round_half_up`).
└── models/ # Pydantic models representing the BSOR file structure.
├── __init__.py # Exports all models from this directory.
├── bsor.py # Main `Bsor` model, orchestrating the entire replay.
├── common.py # Common data structures like `VRObject`, `Position`, `Rotation`.
├── controller_offsets.py # Model for controller offset data.
├── cut.py # Model for `Cut` information (details of a note hit).
├── frame.py # Model for `Frame` data (HMD and controller tracking).
├── height.py # Model for `HeightEvent` data.
├── info.py # Model for `Info` (replay metadata).
├── note.py # Model for `Note` events, `NoteIDData`, `NoteCutInfo`.
├── pause.py # Model for `Pause` events.
├── user_data.py # Model for custom `UserDataEntry` sections.
└── wall.py # Model for `Wall` interaction events.
Installation
You can install the replayed library via pip:
pip install replayed
Getting Started / Basic Usage
Loading a Replay File
You can load a .bsor file using the load_replay function or by directly using the Bsor.from_stream class method.
from replayed import load_replay, BSException
try:
replay_data = load_replay("path/to/your/replay.bsor")
print(f"Successfully loaded replay for player: {replay_data.info.player_name}")
print(f"Song: {replay_data.info.song_name} [{replay_data.info.difficulty}]")
print(f"Score: {replay_data.info.score}")
except FileNotFoundError:
print("Error: Replay file not found.")
except BSException as e:
print(f"Error parsing replay file: {e}")
except Exception as e:
print(f"An unexpected error occurred: {e}")
Accessing Replay Data
Once loaded, the Bsor object provides access to all replay information through its Pydantic model attributes.
# Accessing Info section
player_name = replay_data.info.player_name
song_hash = replay_data.info.song_hash
modifiers = replay_data.info.modifiers
# Accessing Frames (list of Frame objects)
if replay_data.frames:
first_frame = replay_data.frames[0]
print(f"Time of first frame: {first_frame.time}s, FPS: {first_frame.fps}")
print(f"Head position (first frame): x={first_frame.head.position.x}, y={first_frame.head.position.y}, z={first_frame.head.position.z}")
# Accessing Notes (list of Note objects)
if replay_data.notes:
total_notes = len(replay_data.notes)
first_note = replay_data.notes[0]
print(f"Total note events: {total_notes}")
print(f"First note event time: {first_note.event_time}")
# NoteIDData provides decomposed info from the raw note ID
print(f" Scoring Type: {replayed.LOOKUP_DICT_SCORING_TYPE.get(first_note.id_data.scoring_type)}")
print(f" Color Type: {'Blue (Left)' if first_note.id_data.color_type == replayed.SABER_LEFT else 'Red (Right)'}")
if first_note.event_type == replayed.NOTE_EVENT_GOOD and first_note.cut_info:
print(f" Cut Score: {first_note.total_score}")
if first_note.cut_info.cut_details:
print(f" Saber Speed: {first_note.cut_info.cut_details.saber_speed}")
Modifying Replay Data (Conceptual)
Since the data is stored in Pydantic models, you can modify attributes directly. For instance, you could change player name or filter notes (though extensive modifications require careful handling to maintain data integrity for game compatibility).
# Example: Change player name (demonstrative)
# replay_data.info.player_name = "NewPlayerName"
Note: Modifying replay data, especially timing or crucial event data, can easily lead to replays that are invalid or behave unexpectedly in-game.
Saving a Replay File
After modifications (or if you've constructed a Bsor object programmatically), you can save it back to a .bsor file.
from replayed import save_replay
try:
save_replay(replay_data, "path/to/your/modified_replay.bsor")
print("Replay saved successfully.")
except Exception as e:
print(f"Error saving replay: {e}")
Core Data Models
The replayed.models submodule contains Pydantic models that represent the structure of the BSOR file. Each model typically has a from_stream(file_io) class method for parsing and a write(file_io) method for serialization.
-
Bsor: The root model representing the entire replay. It contains all other sections likeInfo, lists ofFrames,Notes, etc.magic_number: intfile_version: intinfo: Infoframes: List[Frame]notes: List[Note]walls: List[Wall]heights: List[HeightEvent]pauses: List[Pause]controller_offsets: Optional[ControllerOffsets]user_data: List[UserDataEntry]
-
Info: Contains metadata about the replay session.player_name: str,player_id: strsong_name: str,song_hash: str,mapper: str,difficulty: strscore: int,modifiers: strgame_version: str,timestamp: str- And many more fields related to tracking, settings, and performance.
-
Frame: Represents a single snapshot of tracking data.time: float(in-game time)fps: inthead: VRObject(HMD position and rotation)left_hand: VRObjectright_hand: VRObject
-
VRObject: A common structure for tracked objects.position: Position(x,y,z)rotation: Rotation(quaternion:x,y,z,w)
-
Note: Represents a note event (cut, miss, or bomb interaction).raw_note_id: int(the encoded ID from the file)event_time: floatspawn_time: floatevent_type: int(e.g.,NOTE_EVENT_GOOD,NOTE_EVENT_MISS)id_data: NoteIDData(computed property: decomposesraw_note_idintoscoring_type,color_type, etc.)cut_info: Optional[NoteCutInfo](containsCutdetails and scores if it was a cuttable note)total_score: int(computed property fromcut_info)
-
Cut: Detailed information about how a note was cut.saber_speed: float,saber_type: intbefore_cut_rating: float,after_cut_rating: floatcut_point: Position,cut_normal: Positionspeed_ok: bool,direction_ok: bool,saber_type_ok: bool
-
Other Models:
Wall: Obstacle interaction events.HeightEvent: Player height change events.Pause: Game pause events.ControllerOffsets: Optional controller offset data.UserDataEntry: For custom data embedded by mods.
Binary I/O (replayed.io_utils)
The io_utils.py module provides low-level functions for reading and writing primitive data types (integers, floats, strings, booleans) from/to the binary BSOR file stream. These are used internally by the Pydantic models' from_stream and write methods. Key functions include:
decode_int(),encode_int()decode_string(),encode_string()decode_string_maybe_utf16()(handles specific string encoding quirks found in some replays)decode_float(),encode_float()- And others for bytes, longs, and booleans.
It also includes generic helpers like write_list and make_list_from_stream (in replayed.utils) for handling lists of objects with their respective magic bytes and counts.
Constants (replayed.constants)
The constants.py file defines various constants used throughout the library and relevant to the Beat Saber game and BSOR format. This includes:
- Note event types (e.g.,
NOTE_EVENT_GOOD,NOTE_EVENT_MISS). - Note scoring types (e.g.,
NOTE_SCORE_TYPE_NORMAL_1,NOTE_SCORE_TYPE_SLIDERHEAD). - Saber types (
SABER_LEFT,SABER_RIGHT). - BSOR magic numbers and section identifiers.
- Lookup dictionaries for converting constant values to human-readable strings (e.g.,
LOOKUP_DICT_EVENT_TYPE).
You can import and use these constants:
import replayed.constants as C
if note.event_type == C.NOTE_EVENT_BOMB:
print("It's a bomb!")
Or, if you've imported replayed, they are often available directly (e.g., replayed.NOTE_EVENT_BOMB).
Error Handling
The library defines a custom exception BSException (in replayed.base_types) which is raised for errors specific to BSOR parsing or data integrity issues (e.g., magic number mismatch, unexpected EOF). Standard Python exceptions like FileNotFoundError or IOError might also be raised by file operations.
Contributing
This library is designed to be a foundational tool. Contributions for bug fixes, new BSOR feature support (as the format evolves), or improved documentation are welcome. Please ensure any changes maintain compatibility and adhere to good coding practices.
Disclaimer
Beat Saber and its replay format are properties of Beat Games. This library is a third-party tool created for parsing and educational purposes.
Project details
Release history Release notifications | RSS feed
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 replayed-0.1.0.tar.gz.
File metadata
- Download URL: replayed-0.1.0.tar.gz
- Upload date:
- Size: 28.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.11.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d9ee0fff40494cfd12422acb18271524aea0915aab958079cd25d3a6a86ad0e4
|
|
| MD5 |
f704e090d47dfeb87c8b4a18b257c59e
|
|
| BLAKE2b-256 |
3ed71ef8978d95acf3a6e93496d512910cbb678f6f22bddf7817ac38a1b8d414
|
File details
Details for the file replayed-0.1.0-py3-none-any.whl.
File metadata
- Download URL: replayed-0.1.0-py3-none-any.whl
- Upload date:
- Size: 30.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.11.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0d5a0e5d5c0c5cdb3607eed75c6e0beda8004384e8e83d16d19baebd4776ebd3
|
|
| MD5 |
698c3e247d0b7d4a2de288fc21d583de
|
|
| BLAKE2b-256 |
15e88d48fb71d7e1b246eccdd5c6edc5e7b5cfadf9eb7a80d9aa036dc584e41f
|