mirror of
https://github.com/Yonokid/PyTaiko.git
synced 2026-02-04 11:40:13 +01:00
Update osz.py
This commit is contained in:
311
libs/parsers/osz.py
Normal file
311
libs/parsers/osz.py
Normal file
@@ -0,0 +1,311 @@
|
||||
import hashlib
|
||||
import math
|
||||
from pathlib import Path
|
||||
|
||||
from libs.parsers.tja import CourseData, Note, NoteType, Drumroll, Balloon, NoteList, TJAEXData, TJAMetadata, TimelineObject
|
||||
|
||||
import re
|
||||
|
||||
class OsuParser:
|
||||
general: dict[str, str]
|
||||
editor: dict[str, str]
|
||||
osu_metadata: dict[str, str]
|
||||
difficulty: dict[str, str]
|
||||
events: list[list[float]]
|
||||
timing_points: list[list[float]]
|
||||
hit_objects: list[list[float]]
|
||||
|
||||
bpm: list[float]
|
||||
|
||||
def __init__(self, osu_file: Path):
|
||||
self.general = self.read_osu_data_dict(osu_file, target_header="General")
|
||||
self.editor = self.read_osu_data_dict(osu_file, target_header="Editor")
|
||||
self.osu_metadata = self.read_osu_data_dict(osu_file, target_header="Metadata")
|
||||
self.difficulty = self.read_osu_data_dict(osu_file, target_header="Difficulty")
|
||||
self.events = self.read_osu_data_list(osu_file, target_header="Events")
|
||||
self.timing_points = self.read_osu_data_list(osu_file, target_header="TimingPoints")
|
||||
#self.general = self.read_osu_data(osu_file, target_header="Colours", is_dict=True)
|
||||
self.hit_objects = self.read_osu_data_list(osu_file, target_header="HitObjects")
|
||||
self.slider_multiplier = float(self.difficulty["SliderMultiplier"])
|
||||
self.metadata = TJAMetadata()
|
||||
self.metadata.wave = osu_file.parent / self.general["AudioFilename"]
|
||||
self.metadata.demostart = float(self.general["PreviewTime"]) / 1000
|
||||
self.metadata.offset = -30/1000
|
||||
self.metadata.title["en"] = self.osu_metadata["Version"]
|
||||
self.metadata.subtitle["en"] = self.osu_metadata["Creator"]
|
||||
match = re.search(r'\[Events\][\s\S]*?^[ \t]*(\d+),(\d+),"([^"]+)"', osu_file.read_text(), re.MULTILINE)
|
||||
if match:
|
||||
self.metadata.bgmovie = osu_file.parent / Path(match.group(3))
|
||||
self.metadata.course_data[0] = CourseData()
|
||||
self.ex_data = TJAEXData()
|
||||
self.bpm = []
|
||||
for points in self.timing_points:
|
||||
self.bpm.append(math.floor(1 / points[1] * 1000 * 60))
|
||||
self.osu_NoteList = self.note_data_to_NoteList(self.hit_objects)
|
||||
for points in self.timing_points:
|
||||
if points[1] > 0:
|
||||
obj = TimelineObject()
|
||||
obj.hit_ms = points[0]
|
||||
obj.bpm = math.floor(1 / points[1] * 1000 * 60)
|
||||
self.osu_NoteList[0].timeline.append(obj)
|
||||
|
||||
def read_osu_data_list(self, file_path: Path, target_header="HitObjects") -> list[list[float]]:
|
||||
data = []
|
||||
current_header = None
|
||||
|
||||
with file_path.open(mode='r', encoding='utf-8') as f:
|
||||
|
||||
for line in f:
|
||||
line = line.rstrip("\n")
|
||||
|
||||
if re.match(r"\[\w*\]", line): # header pattern
|
||||
current_header = line[1:-1]
|
||||
|
||||
if current_header == target_header:
|
||||
|
||||
if re.match(r"[-+]?\d*\.?\d+" , line): # Events, TimingPoints, HitObjects
|
||||
string_array = re.findall(r"[-+]?\d*\.?\d+" , line) # search for floats
|
||||
int_array = [float(num_str) for num_str in string_array]
|
||||
data.append(int_array)
|
||||
|
||||
else:
|
||||
continue
|
||||
|
||||
return data
|
||||
|
||||
def read_osu_data_dict(self, file_path: Path, target_header="HitObjects") -> dict[str, str]:
|
||||
data = dict()
|
||||
current_header = None
|
||||
|
||||
with file_path.open(mode='r', encoding='utf-8') as f:
|
||||
|
||||
for line in f:
|
||||
line = line.rstrip("\n")
|
||||
|
||||
if re.match(r"\[\w*\]", line): # header pattern
|
||||
current_header = line[1:-1]
|
||||
|
||||
if current_header == target_header:
|
||||
if ':' in line and not line.startswith('['):
|
||||
key, value = line.split(':', 1)
|
||||
data[key.strip()] = value.strip()
|
||||
|
||||
else:
|
||||
continue
|
||||
|
||||
return data
|
||||
|
||||
def get_scroll_multiplier(self, ms: float) -> float:
|
||||
base_scroll = (1.0 if 1.37 <= self.slider_multiplier <= 1.47
|
||||
else self.slider_multiplier / 1.40)
|
||||
current_scroll = 1.0
|
||||
|
||||
for tp in self.timing_points:
|
||||
time = tp[0]
|
||||
beat_length = tp[1] # positive for BPM, negative for scroll
|
||||
|
||||
if time > ms:
|
||||
break
|
||||
|
||||
if beat_length < 0: # This is an inherited (green) timing point
|
||||
current_scroll = -100.0 / beat_length
|
||||
|
||||
return current_scroll * base_scroll
|
||||
|
||||
def note_data_to_NoteList(self, note_data) -> tuple[NoteList, list[NoteList], list[NoteList], list[NoteList]]:
|
||||
osu_NoteList = NoteList()
|
||||
counter = 0
|
||||
|
||||
for line in note_data:
|
||||
note_time = line[2]
|
||||
scroll = self.get_scroll_multiplier(note_time)
|
||||
|
||||
if (line[3] == 1 or line[3] == 4 or line[3] == 5 or line[3] == 6) and line[4] == 0: # DON
|
||||
don = Note()
|
||||
don.type = NoteType(1)
|
||||
don.hit_ms = line[2]
|
||||
don.bpm = self.bpm[0]
|
||||
don.scroll_x = scroll
|
||||
don.scroll_y = 0
|
||||
don.display = True
|
||||
don.index = counter
|
||||
counter = counter + 1
|
||||
don.moji = 1
|
||||
|
||||
osu_NoteList.play_notes.append(don)
|
||||
|
||||
if (line[3] == 1 or line[3] == 4 or line[3] == 5 or line[3] == 6) and (line[4] == 2 or line[4] == 8): # KAT
|
||||
kat = Note()
|
||||
kat.type = NoteType(2)
|
||||
kat.hit_ms = line[2]
|
||||
kat.bpm = self.bpm[0]
|
||||
kat.scroll_x = scroll
|
||||
kat.scroll_y = 0
|
||||
kat.display = True
|
||||
kat.index = counter
|
||||
counter = counter + 1
|
||||
kat.moji = 4
|
||||
|
||||
osu_NoteList.play_notes.append(kat)
|
||||
|
||||
if (line[3] == 1 or line[3] == 4 or line[3] == 5 or line[3] == 6) and line[4] == 4: # L-DON
|
||||
don = Note()
|
||||
don.type = NoteType(3)
|
||||
don.hit_ms = line[2]
|
||||
don.bpm = self.bpm[0]
|
||||
don.scroll_x = scroll
|
||||
don.scroll_y = 0
|
||||
don.display = True
|
||||
don.index = counter
|
||||
counter = counter + 1
|
||||
don.moji = 5
|
||||
|
||||
osu_NoteList.play_notes.append(don)
|
||||
|
||||
if (line[3] == 1 or line[3] == 4 or line[3] == 5 or line[3] == 6) and (line[4] == 6 or line[4] == 12): # L-KAT
|
||||
kat = Note()
|
||||
kat.type = NoteType(4)
|
||||
kat.hit_ms = line[2]
|
||||
kat.bpm = self.bpm[0]
|
||||
kat.scroll_x = scroll
|
||||
kat.scroll_y = 0
|
||||
kat.display = True
|
||||
kat.index = counter
|
||||
counter = counter + 1
|
||||
kat.moji = 6
|
||||
|
||||
osu_NoteList.play_notes.append(kat)
|
||||
|
||||
if (line[3] == 2) and (line[4] == 0): # Drum Roll
|
||||
if len(line) >= 9:
|
||||
slider_time = line[8] / (float(self.difficulty["SliderMultiplier"]) * 100) * self.timing_points[0][1]
|
||||
else:
|
||||
slider_time = line[6] / (float(self.difficulty["SliderMultiplier"]) * 100) * self.timing_points[0][1]
|
||||
|
||||
source = Note()
|
||||
source.type = NoteType(8)
|
||||
source.hit_ms = line[2] + slider_time
|
||||
source.bpm = self.bpm[0]
|
||||
source.scroll_x = scroll
|
||||
source.scroll_y = 0
|
||||
source.display = True
|
||||
# this is where the index would be if it wasn't a tail note
|
||||
source.moji = 7
|
||||
|
||||
slider = Drumroll(source)
|
||||
slider.color = 255
|
||||
slider.type = NoteType(5)
|
||||
slider.hit_ms = line[2]
|
||||
slider.bpm = self.bpm[0]
|
||||
slider.scroll_x = scroll
|
||||
slider.scroll_y = 0
|
||||
slider.display = True
|
||||
slider.index = counter
|
||||
slider.moji = 10
|
||||
counter = counter + 1
|
||||
|
||||
source.index = counter
|
||||
counter = counter + 1
|
||||
|
||||
osu_NoteList.play_notes.append(slider)
|
||||
osu_NoteList.play_notes.append(source)
|
||||
|
||||
if (line[3] == 2) and (line[4] == 4): # L-Drum Roll
|
||||
if len(line) >= 9:
|
||||
slider_time = line[8] / (float(self.difficulty["SliderMultiplier"]) * 100) * self.timing_points[0][1]
|
||||
else:
|
||||
slider_time = line[6] / (float(self.difficulty["SliderMultiplier"]) * 100) * self.timing_points[0][1]
|
||||
|
||||
source = Note()
|
||||
source.type = NoteType(8)
|
||||
source.hit_ms = line[2] + slider_time
|
||||
source.bpm = self.bpm[0]
|
||||
source.scroll_x = scroll
|
||||
source.scroll_y = 0
|
||||
source.display = True
|
||||
# this is where the index would be if it wasn't a tail note
|
||||
source.moji = 8
|
||||
|
||||
slider = Drumroll(source)
|
||||
slider.color = 255
|
||||
slider.type = NoteType(6)
|
||||
slider.hit_ms = line[2]
|
||||
slider.bpm = self.bpm[0]
|
||||
slider.scroll_x = scroll
|
||||
slider.scroll_y = 0
|
||||
slider.display = True
|
||||
slider.index = counter
|
||||
counter = counter + 1
|
||||
|
||||
source.index = counter
|
||||
counter = counter + 1
|
||||
|
||||
osu_NoteList.play_notes.append(slider)
|
||||
osu_NoteList.play_notes.append(source)
|
||||
|
||||
if (line[3] == 8): # Balloon
|
||||
source = Note()
|
||||
source.type = NoteType(8)
|
||||
source.hit_ms = line[5]
|
||||
source.bpm = self.bpm[0]
|
||||
source.scroll_x = scroll
|
||||
source.scroll_y = 0
|
||||
source.display = True
|
||||
#source.index = counter
|
||||
#counter = counter + 1
|
||||
source.moji = 9
|
||||
|
||||
balloon = Balloon(source)
|
||||
balloon.type = NoteType(7)
|
||||
balloon.hit_ms = line[2]
|
||||
balloon.bpm = self.bpm[0]
|
||||
balloon.scroll_x = scroll
|
||||
balloon.scroll_y = 0
|
||||
balloon.display = True
|
||||
balloon.index = counter
|
||||
counter = counter + 1
|
||||
balloon.moji = 10
|
||||
|
||||
'''
|
||||
od = int(self.difficulty["OverallDifficulty"])
|
||||
# thank you https://github.com/IepIweidieng/osu2tja/blob/dev-iid/osu2tja/osu2tja.py
|
||||
hit_multiplier = (5 - 2 * (5 - od) / 5 if od < 5
|
||||
else 5 + 2.5 * (od - 5) / 5 if od > 5
|
||||
else 5) * 1.65
|
||||
'''
|
||||
balloon.count = 20#int(max(1, (ret[-1][1] - ret[-2][1]) / 1000 * hit_multiplier))
|
||||
# end of 'stolen' code
|
||||
source.index = counter
|
||||
counter = counter + 1
|
||||
|
||||
osu_NoteList.play_notes.append(balloon)
|
||||
osu_NoteList.play_notes.append(source)
|
||||
|
||||
osu_NoteList.draw_notes = osu_NoteList.play_notes.copy()
|
||||
|
||||
return osu_NoteList, [], [], []
|
||||
|
||||
def notes_to_position(self, difficulty):
|
||||
return self.osu_NoteList
|
||||
|
||||
def hash_note_data(self, notes: NoteList):
|
||||
"""Hashes the note data for the given NoteList."""
|
||||
n = hashlib.sha256()
|
||||
list1 = notes.play_notes
|
||||
list2 = notes.bars
|
||||
merged: list[Note | Drumroll | Balloon] = []
|
||||
i = 0
|
||||
j = 0
|
||||
while i < len(list1) and j < len(list2):
|
||||
if list1[i] <= list2[j]:
|
||||
merged.append(list1[i])
|
||||
i += 1
|
||||
else:
|
||||
merged.append(list2[j])
|
||||
j += 1
|
||||
merged.extend(list1[i:])
|
||||
merged.extend(list2[j:])
|
||||
for item in merged:
|
||||
n.update(item.get_hash().encode('utf-8'))
|
||||
|
||||
return n.hexdigest()
|
||||
Reference in New Issue
Block a user