Giter Club home page Giter Club logo

osu-parsers's Introduction

osu-parsers

CodeFactor License Package

A bundle of decoders/encoders for osu! file formats based on the osu!lazer source code.

  • Written in TypeScript.
  • Based on the osu!lazer source code.
  • Allows you to parse beatmaps of any osu! game mode.
  • Supports beatmap conversion between different game modes.
  • Works in browsers.

Installation

Add a new dependency to your project via npm:

npm install osu-parsers

Dependencies

This package comes with built-in LZMA codec as it is required for replay processing. All classes and their typings can be found in osu-classes package which is a peer dependency and must be installed separately.

Supported file formats

  • .osu - fully supported (decoding/encoding)
  • .osb - fully supported (decoding/encoding)
  • .osr - fully supported (decoding/encoding)

Beatmap decoding

Beatmap decoder is used to read .osu files and convert them to the objects of plain Beatmap type. Note that plain beatmap objects can't be used to get max combo, star rating and performance as they don't have ruleset specific data. To get correct beatmap data, beatmap objects should be converted using one of the supported rulesets.

There are 4 ways to read data using this decoders:

  • via file path - async
  • via data buffer - sync
  • via string - sync
  • via array of split lines - sync

By default, beatmap decoder will decode both beatmap and storyboard. If you want to decode beatmap without storyboard, you can pass false as the second parameter to any of the methods. There is also a support for partial beatmap decoding which allows you to skip unnecessary sections.

Example of beatmap decoding

import { BeatmapDecoder } from 'osu-parsers'

const path = 'path/to/your/file.osu';
const data = 'osu file format v14...';

// This is optional and true by default.
const shouldParseSb = true;

const decoder = new BeatmapDecoder();
const beatmap1 = await decoder.decodeFromPath(path, shouldParseSb);

// Partial beatmap decoding without unnecessary sections.
const beatmap2 = decoder.decodeFromString(data, {
  parseGeneral: false,
  parseEditor: false,
  parseMetadata: false,
  parseDifficulty: false,
  parseEvents: false,
  parseTimingPoints: false,
  parseHitObjects: false,
  parseStoryboard: false,
  parseColours: false,
});

console.log(beatmap1) // Beatmap object.
console.log(beatmap2) // Another Beatmap object.

Storyboard decoding

Storyboard decoder is used to read both .osu and .osb files and convert them to the Storyboard objects.

As in beatmap decoder, there are 4 ways to decode your .osu and .osb files:

  • via file path - async
  • via data buffer - sync
  • via string - sync
  • via array of split lines - sync

Example of storyboard decoding

import { StoryboardDecoder } from 'osu-parsers'

const pathToOsb = 'path/to/your/file.osb';
const pathToOsu = 'path/to/your/file.osu';

const decoder = new StoryboardDecoder();

// Parse a single storyboard file (from .osb) for beatmaps that doesn't have internal storyboard (from .osu)
const storyboard1 = await decoder.decodeFromPath(pathToOsb);

// Combines main storyboard (from .osu) with secondary storyboard (from .osb)
const storyboard2 = await decoder.decodeFromPath(pathToOsu, pathToOsb);

console.log(storyboard1); // Storyboard object.
console.log(storyboard2); // Another Storyboard object.

Score & replay decoding

Score decoder is used to decode .osr files and convert them to the Score objects. Score object contains score information and replay data of plain Replay type. Note that all .osr files contain raw legacy frame data that was initially intended for osu!standard only. To get correct data, replay objects should be converted using one of the supported rulesets. This decoder is based on external LZMA library and works asynchronously.

There are 2 ways to read data through this decoder:

  • via file path - async
  • via buffer - async

By default, score decoder will decode both score information and replay. If you want to decode score information without replay, you can pass false as the second parameter to any of the methods.

Example of score & replay decoding

import { ScoreDecoder } from 'osu-parsers'

const path = 'path/to/your/file.osr';

// This is optional and true by default.
const parseReplay = true;

const decoder = new ScoreDecoder();

const score = await decoder.decodeFromPath(path, parseReplay)

console.log(score.info); // ScoreInfo object.
console.log(score.replay); // Replay object or null.

Encoding

All objects parsed by these decoders can easily be stringified and written to the files. Note that encoders will write object data without any changes. For example, if you try to encode beatmap with applied mods, it will write modded values!

When encoding is complete you can import resulting files to the game.

Example of beatmap encoding

import { BeatmapDecoder, BeatmapEncoder } from 'osu-parsers'

const decodePath = 'path/to/your/file.osu';
const shouldParseSb = true;

const decoder = new BeatmapDecoder();
const encoder = new BeatmapEncoder();

const beatmap = await decoder.decodeFromPath(decodePath, shouldParseSb);

// Do your stuff with beatmap...

const encodePath = 'path/to/your/file.osu';

// Write IBeatmap object to an .osu file.
await encoder.encodeToPath(encodePath, beatmap);

// You can also encode IBeatmap object to a string.
const stringified = encoder.encodeToString(beatmap);

Example of storyboard encoding

import { StoryboardDecoder, StoryboardEncoder } from 'osu-parsers'

const decodePath = 'path/to/your/file.osb';

const decoder = new StoryboardDecoder();
const encoder = new StoryboardEncoder();

const storyboard = await decoder.decodeFromPath(decodePath);

// Do your stuff with storyboard...

const encodePath = 'path/to/your/file.osb';

// Write Storyboard object to an .osb file.
await encoder.encodeToPath(encodePath, storyboard);

// You can also encode Storyboard object to a string.
const stringified = encoder.encodeToString(storyboard);

Example of score & replay encoding

import { ScoreDecoder, ScoreEncoder } from 'osu-parsers'

const decodePath = 'path/to/your/file.osr';

const decoder = new ScoreDecoder();
const encoder = new ScoreEncoder();

const score = await decoder.decodeFromPath(decodePath);

// Do your stuff with score info and replay...

const encodePath = 'path/to/your/file.osr';

// Write IScore object to an .osr file.
await encoder.encodeToPath(encodePath, score);

// You can also encode IScore object to a buffer.
const buffer = await encoder.encodeToBuffer(score);

Rulesets

This library by itself doesn't provide any tools for difficulty and performance calculation!!!! If you are looking for something related to this, then rulesets may come in handy for you. Rulesets are separate libraries based on the classes from the osu-classes project. They allow you to work with gamemode specific stuff as difficulty, performance, mods and max combo calculation. Because of the shared logic between all of the rulesets they are compatible between each other. If you want, you can even write your own custom ruleset! The great thing about all this stuff is a beatmap and replay conversion. Any beatmap or replay can be used with any ruleset library as long as they implement the same interfaces.

There are 4 basic rulesets that support parsed beatmaps from this decoder:

You can also try existing osu-pp-calculator package. It's a wrapper for all 4 rulesets above with a lot of extra useful stuff like score simulation and beatmap downloading.

Documentation

Auto-generated documentation is available here.

Contributing

This project is being developed personally by me on pure enthusiasm. If you want to help with development or fix a problem, then feel free to create a new pull request. For major changes, please open an issue first to discuss what you would like to change.

License

This project is licensed under the MIT License - see the LICENSE for details.

osu-parsers's People

Contributors

blueberrymuffin3 avatar kionell avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

osu-parsers's Issues

Beatmaps with `:` in a metadata field gets the `:` replaced with a space

When parsing a beatmap that happens to have a colon : in a metadata field results in the colon being replaced with a space:

Original .osu Parsed String (console.log) Parsed Object (console.log)
image image image

Code used to parse the beatmap

const importMetadata = (originBeatmap: string): BeatmapMetadata => {
  const decoder = new BeatmapDecoder();

  const decodedOriginBeatmap = decoder.decodeFromString(originBeatmap, {
    parseStoryboard: true,
  });

  console.log(decodedOriginBeatmap);

  return {
    metadata: {
      artist: decodedOriginBeatmap.metadata.artistUnicode,
      artistRomanized: decodedOriginBeatmap.metadata.artist,
      title: decodedOriginBeatmap.metadata.titleUnicode,
      titleRomanized: decodedOriginBeatmap.metadata.title,
      creator: decodedOriginBeatmap.metadata.creator,
      source: decodedOriginBeatmap.metadata.source,
      tags: decodedOriginBeatmap.metadata.tags,
    },
    generalMetadata: {
      audioFileName: decodedOriginBeatmap.general.audioFilename,
      previewTime: decodedOriginBeatmap.general.previewTime,
      epilepsyWarning: decodedOriginBeatmap.general.epilepsyWarning,
      samplesMatchPlaybackRate:
        decodedOriginBeatmap.general.samplesMatchPlaybackRate,
      widescreenStoryboard: decodedOriginBeatmap.general.widescreenStoryboard,
    },
    beatmapColours: {
      sliderBodyColour: decodedOriginBeatmap.colors.sliderBorderColor,
      sliderTrackColour: decodedOriginBeatmap.colors.sliderTrackColor,
      comboColours: decodedOriginBeatmap.colors.comboColors,
    },
  };
};

related .osu

Add support for browser environment

There are some parts of the code that don't work in the browser:

  1. All decodeFromPath and encodeToPath methods - fs module problem.
  2. Both score decoder and encoder - buffer methods.

ScoreDecoder is not working in Browser environiment

There's no Buffer in the browser, hence the reason replays fail to decode.
A workaround is to use the "buffer" module from npm.

Workaround example:

import { Buffer } from "buffer";
async function getReplay(){
  const requestResponse = await fetch("/replay.osr")
  const arrayBuffer = await requestResponse.arrayBuffer()
  await decoder.decodeFromBuffer(Buffer.from(arrayBuffer));
}

It would be way better to have decodeFromBuffer accept ArrayBuffer.

use error

安装后提示osu-parsers/lib/browser.mjs 为起点找不到模块 "osu-classes"

Empty first line in a map throws "Not a valid beatmap!" error

If there is an empty line above the osu file format v line, the parser throws Error: Failed to decode a beatmap: Not a valid beatmap!.

This only really appeared in 1 map out of the ~2,500 I've parsed (https://osu.ppy.sh/b/1485848), so it's not a very big problem as far as I know.

image

A quick fix I tried adding was:

this._reset();
this._lines = this._getLines(data);
this._setEnabledSections(typeof options !== 'boolean' ? options : {});
this._sbLines = this._shouldParseStoryboard(options) ? [] : null;

// Remove first line if it's empty
if (this._lines[0] == '') this._lines.shift()

const fileFormatLine = this._lines[0].trim();

if (!fileFormatLine.startsWith('osu file format v')) {
  throw new Error('Not a valid beatmap!');
}

inside the decodeFromLines() function at node.cjs:2700 and node.mjs:2696.
or at BeatmapDecoder.ts:121.

Score encoder is not working

Buffers cannot change their size dynamically as arrays. Because of this problem, score encoder crashes the program. Serialization writer needs refactoring.

Encoding a beatmap with video does not write the video to the encoded string/file.

I'm building a cross-platform hitsound copier and i got into some undefined behaviour after encoding a beatmap with video.

Debug info of BeatmapEventSection right before calling encoder.encodeToString() Generated content in the .osu
image image

Code used to encode it.

  const hitsoundedBeatmap = beatmapsTo.map((beatmap) =>
    copyHitsounds(beatmapFrom, beatmap, options)
  );

  const encoder = new BeatmapEncoder();

  const encodedHitsoundedBeatmaps = hitsoundedBeatmap.map((beatmap) =>
    encoder.encodeToString(beatmap)
  );

Storyboard parser fails on commands with an empty end time

Example Map: https://osu.ppy.sh/beatmapsets/653534#osu/1464026

The offending lines look like this:
https://gist.github.com/blueberrymuffin3/fd924a80054a129231f91651d1a42788#file-panda-eyes-ily-m-a-r-v-o-l-l-o-light-insane-osu-L48-L70

 M,0,454,,117,104

These commands are correctly parsed by osu!stable and osu!lazer as zero-duration commands, but are ignored by StoryboardParser.
It's also referenced in the osu!wiki here.

The error occurs here:

if (Number.isNaN(value)) {
throw new Error('Not a number');
}

It's caused by this line:
command.endTime = Parsing.parseInt(data[3]);

And is caught here:
try {
// Storyboard data.
this._parseStoryboardData(line, storyboard, depth);
}
catch {
return;
}

Add combo offset to the parsed objects

osu! uses additional hit object types (ComboSkip1, ComboSkip2, ComboSkip3) to add offset to combo indexes. At this moment beatmap parser doesn't support it.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.