Skip to main content

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 BSException for 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 like Info, lists of Frames, Notes, etc.

    • magic_number: int
    • file_version: int
    • info: Info
    • frames: 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: str
    • song_name: str, song_hash: str, mapper: str, difficulty: str
    • score: int, modifiers: str
    • game_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: int
    • head: VRObject (HMD position and rotation)
    • left_hand: VRObject
    • right_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: float
    • spawn_time: float
    • event_type: int (e.g., NOTE_EVENT_GOOD, NOTE_EVENT_MISS)
    • id_data: NoteIDData (computed property: decomposes raw_note_id into scoring_type, color_type, etc.)
    • cut_info: Optional[NoteCutInfo] (contains Cut details and scores if it was a cuttable note)
    • total_score: int (computed property from cut_info)
  • Cut: Detailed information about how a note was cut.

    • saber_speed: float, saber_type: int
    • before_cut_rating: float, after_cut_rating: float
    • cut_point: Position, cut_normal: Position
    • speed_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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

replayed-0.1.0.tar.gz (28.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

replayed-0.1.0-py3-none-any.whl (30.8 kB view details)

Uploaded Python 3

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

Hashes for replayed-0.1.0.tar.gz
Algorithm Hash digest
SHA256 d9ee0fff40494cfd12422acb18271524aea0915aab958079cd25d3a6a86ad0e4
MD5 f704e090d47dfeb87c8b4a18b257c59e
BLAKE2b-256 3ed71ef8978d95acf3a6e93496d512910cbb678f6f22bddf7817ac38a1b8d414

See more details on using hashes here.

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

Hashes for replayed-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0d5a0e5d5c0c5cdb3607eed75c6e0beda8004384e8e83d16d19baebd4776ebd3
MD5 698c3e247d0b7d4a2de288fc21d583de
BLAKE2b-256 15e88d48fb71d7e1b246eccdd5c6edc5e7b5cfadf9eb7a80d9aa036dc584e41f

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page