Random track group are now supported in ct_config.json, cup icons can now be named by the cup name, custom track cup now start at 001

This commit is contained in:
Faraphel 2022-01-21 16:39:29 +01:00
parent 5111689872
commit 03e85fb442
44 changed files with 279 additions and 189 deletions

View file

@ -1805,6 +1805,37 @@
}
],
"tracks_list":[
{
"name":"4IT Clown's Road",
"author":[
"4IT★Lecce",
"4IT★Clown"
],
"special":"T82",
"music":"T82",
"score":2,
"since_version":"0.8",
"sha1":"f9dbbaca67b7a26c7986a6e99c2a56cf9fdfa14f",
"version":"v1.2",
"family":2700,
"tags":[
]
},
{
"name":"8'n Infinite Race",
"author":"Bulzeeb",
"special":"T11",
"music":"T21",
"score":2,
"since_version":"0.7",
"sha1":"3566c3453af382ac13b9db813d8b44286d1e281f",
"version":"v1.1",
"family":3020,
"tags":[
]
},
{
"name":"4IT Clown's Road",
"author":[

View file

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View file

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View file

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View file

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View file

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View file

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View file

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View file

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View file

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

View file

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

View file

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

View file

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View file

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View file

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View file

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View file

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View file

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View file

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View file

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View file

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

View file

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View file

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View file

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

View file

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

View file

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View file

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

View file

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

View file

@ -155,6 +155,7 @@
"bmg_patch_dir": "/generated/",
"cup_icon_dir": "/cup_icon/",
"lecode_bin_dir": "/essentials/",
"lpar_dir": "/essentials/"
"lpar_dir": "/essentials/",
"track_dir": "/Track-WU8/"
}
}

View file

@ -1,16 +1,16 @@
from PIL import Image, ImageDraw, ImageFont
from PIL import Image
import math
import json
import os
from source.Cup import Cup
from source.Track import Track, HiddenTrackAttr
from source.Track import Track, get_trackdata_from_json
class CT_Config:
def __init__(self, version: str = None, name: str = None, nickname: str = None,
game_variant: str = "01", gui=None, region: int = None, cheat_region: int = None,
tags_color: dict = {}, prefix_list: list = [], suffix_list: list = [],
tags_color: dict = None, prefix_list: list = None, suffix_list: list = None,
tag_retro: str = "Retro", default_track: Track = None, pack_path: str = "",
file_process: dict = None, file_structure: dict = None):
@ -23,12 +23,11 @@ class CT_Config:
self.ordered_cups = []
self.unordered_tracks = []
self.all_tracks = []
self.all_version = {version}
self.gui = gui
self.tags_color = tags_color
self.prefix_list = prefix_list
self.suffix_list = suffix_list
self.tags_color = tags_color if tags_color else {}
self.prefix_list = prefix_list if tags_color else []
self.suffix_list = suffix_list if tags_color else []
self.tag_retro = tag_retro
self.default_track = default_track
@ -43,9 +42,6 @@ class CT_Config:
:param cup: a Cup object to add as an ordered cup
"""
self.ordered_cups.append(cup)
for track in cup.tracks:
self.all_version.add(track.since_version)
self.all_tracks.append(track)
def add_unordered_track(self, track: Track) -> None:
"""
@ -53,13 +49,19 @@ class CT_Config:
:param track: a Track object to add as an unordered tracks
"""
self.unordered_tracks.append(track)
self.all_version.add(track.since_version)
self.all_tracks.append(track)
def create_ctfile(self, directory: str = "./file/", highlight_version: str = None, sort_track_by: str = None) -> None:
def unordered_tracks_to_cup(self):
track_in_cup: int = 4
for cup_id, track_id in enumerate(range(0, len(self.unordered_tracks), track_in_cup), start=1):
cup = Cup(id=cup_id, name=f"CT{cup_id}")
for index, track in enumerate(self.unordered_tracks[track_id:track_id + track_in_cup]):
cup.tracks[index] = track
yield cup
def create_ctfile(self, directory: str = "./file/", highlight_version: str = None) -> None:
"""
create a ctfile configuration in a directory
:param sort_track_by: by which property will track be sorted
:param highlight_version: highlight a specific version in light blue
:param directory: create CTFILE.txt and RCTFILE.txt in this directory
"""
@ -74,54 +76,31 @@ class CT_Config:
)
ctfile.write(header); rctfile.write(header)
# generate cup for undefined track
unordered_cups = []
star_value = []
if not self.gui.boolvar_use_1star_track.get(): star_value.append(1)
if not self.gui.boolvar_use_2star_track.get(): star_value.append(2)
if not self.gui.boolvar_use_3star_track.get(): star_value.append(3)
track_list = self.search_tracks(not_value=True, values_list=True,
only_unordered_track=True, score=star_value)
if sort_track_by:
track_list.sort(key=lambda track: getattr(track, sort_track_by, None))
for i, track in enumerate(track_list):
if i % 4 == 0:
_actual_cup = Cup(name=f"TL{i // 4}", default_track=self.default_track)
unordered_cups.append(_actual_cup)
_actual_cup.tracks[i % 4] = track
# all cups
for cup in self.ordered_cups + unordered_cups:
for cup in self.get_all_cups():
kwargs = {"highlight_version": highlight_version, "ct_config": self}
ctfile.write(cup.get_ctfile_cup(race=False, **kwargs))
rctfile.write(cup.get_ctfile_cup(race=True, **kwargs))
ctfile.write(cup.get_ctfile(race=False, **kwargs))
rctfile.write(cup.get_ctfile(race=True, **kwargs))
def get_cup_icon(self, cup_id: [str, int], font_path: str = "./assets/SuperMario256.ttf") -> Image:
"""
:param cup_id: id of the cup
:param font_path: path to the font used to generate icon
:return: cup icon
"""
dir = self.file_process['placement'].get('cup_icon_dir')
if not dir: dir = "/ct_icons/"
cup_icon_dir = f"{self.pack_path}/file/{dir}/"
if os.path.exists(f"{cup_icon_dir}/{cup_id}.png"):
cup_icon = Image.open(f"{cup_icon_dir}/{cup_id}.png").resize((128, 128))
def get_tracks(self):
for data in self.unordered_tracks + self.ordered_cups:
for track in data.get_tracks(): yield track
def search_tracks(self, **condition):
for track in self.get_tracks():
for property, (possibility, filter_func) in condition.items():
if not filter_func: filter_func = lambda a, b: a == b
if not filter_func(getattr(track, property, None), possibility):
break
else:
cup_icon = Image.new("RGBA", (128, 128))
draw = ImageDraw.Draw(cup_icon)
font = ImageFont.truetype(font_path, 90)
draw.text((4, 4), "CT", (255, 165, 0), font=font, stroke_width=2, stroke_fill=(0, 0, 0))
font = ImageFont.truetype(font_path, 60)
draw.text((5, 80), "%03i" % cup_id, (255, 165, 0), font=font, stroke_width=2, stroke_fill=(0, 0, 0))
yield track
return cup_icon
def get_cup_count(self) -> int:
return math.ceil(len(self.unordered_tracks) / 4) + len(self.ordered_cups)
def get_all_cups(self):
for cup in self.ordered_cups: yield cup
for cup in self.unordered_tracks_to_cup(): yield cup
def get_cticon(self) -> Image:
"""
@ -129,18 +108,27 @@ class CT_Config:
:return: ct_icon image
"""
CT_ICON_WIDTH = 128
icon_files = ["left", "right"]
total_cup_count = math.ceil(len(self.all_tracks) // 4) + 10 # +10 because left, right, start at 0, 8 normal cup
default_cups = [
"left", "right",
"mushroom", "shell", "flower", "banana",
"star", "leaf", "special", "lightning",
"random"
]
total_cup_count = self.get_cup_count() + len(default_cups) # +10 because left, right, start at 0, 8 normal cup
ct_icon = Image.new("RGBA", (CT_ICON_WIDTH, CT_ICON_WIDTH * (total_cup_count + 2)))
# +2 because of left and right arrow
icon_files.extend(range(total_cup_count))
def icon_cups_generator():
for id, name in enumerate(default_cups): yield Cup(name=name, id=-id) # default cup have a negative id
for cup in self.get_all_cups(): yield cup
for index, cup_id in enumerate(icon_files):
for index, cup in enumerate(icon_cups_generator()):
# index is a number, id can be string or number ("left", 0, 12, ...)
cup_icon = self.get_cup_icon(cup_id)
ct_icon.paste(cup_icon, (0, index * CT_ICON_WIDTH))
ct_icon.paste(cup.get_icon(), (0, index * CT_ICON_WIDTH))
return ct_icon
@ -167,21 +155,30 @@ class CT_Config:
self.pack_path = pack_path
# default track
self.default_track = Track(track_wu8_dir=f"{self.pack_path}/file/Track-WU8/")
if "default_track" in ctconfig_json: self.default_track.load_from_json(ctconfig_json["default_track"])
with open(f"{pack_path}/file_process.json", encoding="utf8") as fp_file:
self.file_process = json.load(fp_file)
with open(f"{pack_path}/file_structure.json", encoding="utf8") as fs_file:
self.file_structure = json.load(fs_file)
for cup_json in ctconfig_json["cup"] if "cup" in ctconfig_json else []: # tracks with defined order
cup = Cup(default_track=self.default_track)
dir = self.file_process['placement'].get('cup_icon_dir') if 'placement' in self.file_process else None
if not dir: dir = "/ct_icons/"
Cup.icon_dir = f"{self.pack_path}/file/{dir}/"
# default track
self.default_track = Track()
if "default_track" in ctconfig_json: self.default_track.load_from_json(ctconfig_json["default_track"])
Cup.default_track = self.default_track
for id, cup_json in enumerate(ctconfig_json["cup"] if "cup" in ctconfig_json else []):
# tracks with defined order
cup = Cup(id=id)
cup.load_from_json(cup_json)
self.ordered_cups.append(cup)
self.all_tracks.extend(cup.tracks)
for track_json in ctconfig_json["tracks_list"] if "tracks_list" in ctconfig_json else []: # unordered tracks
track = Track(track_wu8_dir=f"{self.pack_path}/file/Track-WU8/")
track.load_from_json(track_json)
for track_json in ctconfig_json["tracks_list"] if "tracks_list" in ctconfig_json else []:
# unordered tracks
track = get_trackdata_from_json(track_json)
self.unordered_tracks.append(track)
self.all_tracks.append(track)
self.version = ctconfig_json.get("version")
@ -192,39 +189,23 @@ class CT_Config:
for param in ["region", "cheat_region", "tags_color", "prefix_list", "suffix_list", "tag_retro"]:
setattr(self, param, ctconfig_json.get(param))
with open(f"{pack_path}/file_process.json", encoding="utf8") as fp_file:
self.file_process = json.load(fp_file)
with open(f"{pack_path}/file_structure.json", encoding="utf8") as fs_file:
self.file_structure = json.load(fs_file)
wu8_dirname = self.file_process["track_dir"] if "track_dir" in self.file_process else "/Track-WU8/"
Track._wu8_dir = f"{self.pack_path}/file/{wu8_dirname}/"
Track._szs_dir = "./file/Track/"
Track.tag_retro = self.tag_retro
Track.prefix_list = self.prefix_list
Track.suffix_list = self.suffix_list
Track.tags_color = self.tags_color
return self
def search_tracks(self, values_list=False, not_value=False, only_unordered_track=False, **kwargs) -> list:
"""
:param only_unordered_track: only search in unordered track
:param values_list: search track with a value list instead of a single value
:param not_value: search track that does not have value
:param kwargs: any track property = any value
:return: track list respecting condition
"""
track = self.all_tracks.copy() if not only_unordered_track else self.unordered_tracks.copy()
if values_list:
if not_value: filter_func = lambda track: getattr(track, keyword) not in value
else: filter_func = lambda track: getattr(track, keyword) in value
else:
if not_value: filter_func = lambda track: getattr(track, keyword) != value
else: filter_func = lambda track: getattr(track, keyword) == value
for keyword, value in kwargs.items():
track = list(filter(filter_func, track))
return track
def get_tracks_count(self) -> int:
return sum(1 for _ in self.get_tracks())
def get_all_track_possibilities(self) -> dict:
possibilities = {}
for track in self.all_tracks:
for track in self.get_tracks():
for key, value in track.__dict__.items():
if key in HiddenTrackAttr: continue
if not key in possibilities: possibilities[key] = []
if type(value) == list:

View file

@ -1,14 +1,20 @@
from source.Track import Track
from PIL import Image, ImageDraw, ImageFont
import os
from source.Track import Track, TrackGroup, get_trackdata_from_json
class Cup:
icon_dir = None
default_track = None
def __init__(self,
default_track: Track,
name: str = None,
track1: Track = None,
track2: Track = None,
track3: Track = None,
track4: Track = None,
id: int = 0,
track1: [Track, TrackGroup] = None,
track2: [Track, TrackGroup] = None,
track3: [Track, TrackGroup] = None,
track4: [Track, TrackGroup] = None,
*args, **kwargs):
"""
class of a cup
@ -22,14 +28,15 @@ class Cup:
"""
self.name = name
self.id = id
self.tracks = [
track1 if track1 else default_track.copy(),
track2 if track2 else default_track.copy(),
track3 if track3 else default_track.copy(),
track4 if track4 else default_track.copy()
track1 if track1 else self.default_track.copy() if self.default_track else None,
track2 if track2 else self.default_track.copy() if self.default_track else None,
track3 if track3 else self.default_track.copy() if self.default_track else None,
track4 if track4 else self.default_track.copy() if self.default_track else None,
]
def get_ctfile_cup(self, *args, **kwargs) -> str:
def get_ctfile(self, *args, **kwargs) -> str:
"""
get the ctfile definition for the cup
:param race: is it a text used for Race_*.szs ?
@ -40,7 +47,7 @@ class Cup:
ctfile_cup += track.get_ctfile(*args, **kwargs)
return ctfile_cup
def load_from_json(self, cup: dict):
def load_from_json(self, cup: dict, *args, **kwargs):
"""
load the cup from a dictionnary
:param cup: dictionnary cup
@ -48,8 +55,35 @@ class Cup:
for key, value in cup.items(): # load all value in the json as class attribute
if key == "tracks": # if the key is tracks
for i, track_json in enumerate(value): # load all tracks from their json
self.tracks[i].load_from_json(track_json)
self.tracks[i] = get_trackdata_from_json(track_json)
else:
setattr(self, key, value)
return self
def get_tracks(self):
for trackdata in self.tracks:
for track in trackdata.get_tracks():
yield track
def get_icon(self, font_path: str = "./assets/SuperMario256.ttf") -> Image:
"""
:param font_path: path to the font used to generate icon
:return: cup icon
"""
cup_icon = None
for name in [self.id, self.name]:
if os.path.exists(f"{self.icon_dir}/{name}.png"):
cup_icon = Image.open(f"{self.icon_dir}/{name}.png").resize((128, 128))
if not cup_icon:
cup_icon = Image.new("RGBA", (128, 128))
draw = ImageDraw.Draw(cup_icon)
font = ImageFont.truetype(font_path, 90)
draw.text((4, 4), "CT", (255, 165, 0), font=font, stroke_width=2, stroke_fill=(0, 0, 0))
font = ImageFont.truetype(font_path, 60)
draw.text((5, 80), "%03i" % self.id, (255, 165, 0), font=font, stroke_width=2, stroke_fill=(0, 0, 0))
return cup_icon

View file

@ -29,11 +29,6 @@ class TooMuchSha1CheckFailed(Exception):
super().__init__("Too much sha1 check failed !")
class CantConvertTrack(Exception):
def __init__(self):
super().__init__("Can't convert track.")
class NoGui:
"""
'fake' gui if no gui are used for compatibility.
@ -280,7 +275,7 @@ class Game:
if not lpar_path: f""
lpar_path = (
f"{self.ctconfig.pack_path}/file/{lpar_path}/"
f"lpar-{'debug' if self.gui.boolvar_use_debug_mode.get() else 'normal'}"
f"lpar-{'debug' if self.gui.boolvar_use_debug_mode.get() else 'normal'}.txt"
)
lecode_file = self.ctconfig.file_process["placement"].get("lecode_bin_dir")
@ -493,7 +488,6 @@ class Game:
self.gui.progress(statut=self.gui.translate("Configurating LE-CODE"), add=1)
self.ctconfig.create_ctfile(
highlight_version=self.gui.stringvar_mark_track_from_version.get(),
sort_track_by=self.gui.stringvar_sort_track_by.get()
)
self.generate_cticons()
@ -614,16 +608,9 @@ class Game:
"""
nonlocal error_count, error_max, thread_list
if os.path.exists(track.file_szs) and os.path.getsize(track.file_szs) < 1000:
os.remove(track.file_szs) # File under this size are corrupted
if not track.check_szs_sha1(): # if sha1 of track's szs is incorrect or track's szs does not exist
if os.path.exists(track.file_wu8):
track.convert_wu8_to_szs()
else:
messagebox.showerror(self.gui.translate("Error"),
self.gui.translate("Can't convert track.\nEnable track download and retry."))
raise CantConvertTrack()
try: track.convert_wu8_to_szs()
except Exception as e:
raise e
def clean_process() -> int:
"""
@ -639,10 +626,10 @@ class Game:
return bool(thread_list)
total_track = len(self.ctconfig.all_tracks)
total_track = self.ctconfig.get_tracks_count()
self.gui.progress(max=total_track, indeter=False, show=True)
for i, track in enumerate(self.ctconfig.all_tracks):
for i, track in enumerate(self.ctconfig.get_tracks()):
while error_count <= error_max:
if len(thread_list) < max_process:
thread_list[track.sha1] = Thread(target=add_process, args=[track])

View file

@ -2,19 +2,25 @@ from source.definition import *
from source.wszst import *
HiddenTrackAttr = [
"file_wu8",
"file_szs",
"_track_wu8_dir",
"_track_szs_dir"
] # These attribute shouldn't be used to reference all the possibilities of values
class CantDownloadTrack(Exception):
def __init__(self, track, http_error: [str, int]):
super().__init__(f"Can't download track {track.name} ({track.sha1}) (error {http_error}) !")
class CantConvertTrack(Exception):
def __init__(self):
super().__init__("Can't convert track.")
class MissingTrackWU8(Exception):
def __init__(self):
super().__init__("The original wu8 track file is missing !")
def get_trackdata_from_json(track_json, *args, **kwargs):
return (TrackGroup if "group" in track_json else Track)(*args, **kwargs).load_from_json(track_json)
def check_file_sha1(file: str, excepted_sha1: str) -> int:
"""
check if track szs sha1 is correct
@ -22,17 +28,24 @@ def check_file_sha1(file: str, excepted_sha1: str) -> int:
"""
if not os.path.exists(file): return 0
calculated_sha1 = szs.sha1(file=file)
if calculated_sha1 == excepted_sha1: return 1
if calculated_sha1 == excepted_sha1:
return 1
else:
print(f"incorrect sha1 for {file} {calculated_sha1} : (expected {excepted_sha1})")
return 0
class Track:
_wu8_dir = None
_szs_dir = None
tag_retro = None
prefix_list = None
suffix_list = None
tags_color = None
def __init__(self, name: str = " ", author: str = "Nintendo", special: str = "T11", music: str = "T11",
sha1: str = None, since_version: str = None, score: int = -1, warning: int = 0, note: str = "",
track_wu8_dir: str = "./file/Track-WU8/", track_szs_dir: str = "./file/Track/",
version: str = None, tags: list = [], *args, **kwargs):
sha1: str = None, since_version: str = None, score: int = -1, warning: int = 0,
version: str = None, tags: list = None, is_in_group: bool = False, *args, **kwargs):
"""
Track class
:param name: track name
@ -66,12 +79,10 @@ class Track:
self.score = score # Track score between 1 and 3 stars
self.warning = warning # Track bug level (1 = minor, 2 = major)
self.version = version
self.tags = tags
self.tags = tags if tags else []
self.refresh_filename()
self._track_wu8_dir = track_wu8_dir
self._track_szs_dir = track_szs_dir
self.file_wu8 = f"{track_wu8_dir}/{self.sha1}.wu8"
self.file_szs = f"{track_szs_dir}/{self.sha1}.szs"
self._is_in_group = is_in_group
def __repr__(self) -> str:
"""
@ -85,23 +96,33 @@ class Track:
check if track wu8 sha1 is correct
:return: 0 if yes, -1 if no
"""
return check_file_sha1(self.file_wu8, self.sha1)
return check_file_sha1(self._wu8_file, self.sha1)
def check_szs_sha1(self) -> int:
"""
check if track szs sha1 is correct
:return: 0 if yes, -1 if no
"""
return check_file_sha1(self.file_szs, self.sha1)
return check_file_sha1(self._szs_file, self.sha1)
def convert_wu8_to_szs(self) -> None:
"""
convert track to szs
"""
file_wu8 = f"{self._wu8_dir}/{self.sha1}.wu8"
file_szs = f"{self._szs_dir}/{self.sha1}.szs"
if os.path.exists(file_szs) and os.path.getsize(file_szs) < 1000:
os.remove(file_szs) # File under this size are corrupted
if not self.check_szs_sha1(): # if sha1 of track's szs is incorrect or track's szs does not exist
if os.path.exists(file_wu8):
szs.normalize(
src_file=self.file_wu8,
dest_dir="./file/Track/"
src_file=file_wu8,
dest_file=file_szs
)
else:
raise MissingTrackWU8()
def get_author_str(self) -> str:
"""
@ -109,39 +130,43 @@ class Track:
"""
return self.author if type(self.author) == str else ", ".join(self.author)
def get_ctfile(self, ct_config, race=False, *args, **kwargs) -> str:
def get_ctfile(self, race=False, *args, **kwargs) -> str:
"""
get ctfile text to create CTFILE.txt and RCTFILE.txt
:param ct_config: ct_config used to generate the Track
:param race: is it a text used for Race_*.szs ?
:return: ctfile definition for the track
"""
ctfile_text = (
f' T {self.music}; '
f'{self.special}; '
f'{"0x00" if ct_config.tag_retro in self.tags else "0x01"}; '
)
if not race:
ctfile_text += (
f'"{self.sha1}"; ' # track path
f'"{self.get_track_formatted_name(ct_config, *args, **kwargs)}"; ' # track text shown ig
f'"{self.sha1}"\n') # sha1
else:
ctfile_text += (
track_type = "T"
track_flag = 0x00 if self.tag_retro in self.tags else 0x01
if self._is_in_group:
track_type = "H"
track_flag |= 0x04
ctfile_track = f' {track_type} {self.music}; {self.special}; {hex(track_flag)}; '
if race:
ctfile_track += (
f'"-"; ' # track path, not used in Race_*.szs, save a bit of space
f'"{self.get_track_formatted_name(ct_config, *args, **kwargs)}\\n{self.get_author_str()}"; '
f'"{self.get_track_formatted_name(*args, **kwargs)}\\n{self.get_author_str()}"; '
# only in race show author's name
f'"-"\n' # sha1, not used in Race_*.szs, save a bit of space
)
return ctfile_text
else:
ctfile_track += (
f'"{self.sha1}"; ' # track path
f'"{self.get_track_formatted_name(*args, **kwargs)}"; ' # track text shown ig
f'"{self.sha1}"\n' # sha1
)
return ctfile_track
def select_tag(self, tag_list: list) -> str:
for tag in self.tags:
if tag in tag_list: return tag
return ""
def get_track_formatted_name(self, ct_config, highlight_version: str = None, *args, **kwargs) -> str:
def get_track_formatted_name(self, highlight_version: str = None, *args, **kwargs) -> str:
"""
get the track name with score, color, ...
:param ct_config: ct_config for tags configuration
@ -150,8 +175,8 @@ class Track:
"""
hl_prefix = "" # highlight
hl_suffix = ""
prefix = self.select_tag(ct_config.prefix_list) # tag prefix
suffix = self.select_tag(ct_config.suffix_list) # tag suffix
prefix = self.select_tag(self.prefix_list) # tag prefix
suffix = self.select_tag(self.suffix_list) # tag suffix
star_prefix = "" # star
star_suffix = ""
star_text = ""
@ -166,8 +191,8 @@ class Track:
if self.since_version == highlight_version:
hl_prefix, hl_suffix = "\\\\c{blue1}", "\\\\c{off}"
if prefix: prefix = "\\\\c{"+ct_config.tags_color[prefix]+"}"+prefix+"\\\\c{off} "
if suffix: suffix = " (\\\\c{"+ct_config.tags_color[suffix]+"}"+suffix+"\\\\c{off})"
if prefix: prefix = "\\\\c{" + self.tags_color[prefix] + "}" + prefix + "\\\\c{off} "
if suffix: suffix = " (\\\\c{" + self.tags_color[suffix] + "}" + suffix + "\\\\c{off})"
name = f"{star_prefix}{star_text}{star_suffix}{prefix}{hl_prefix}{self.name}{hl_suffix}{suffix}"
return name
@ -179,6 +204,10 @@ class Track:
"""
return f"{self.select_tag(ct_config.prefix_list)}{self.name}{self.select_tag(ct_config.suffix_list)}"
def refresh_filename(self):
self._wu8_file = f"{self._wu8_dir}/{self.sha1}.wu8"
self._szs_file = f"{self._szs_dir}/{self.sha1}.szs"
def load_from_json(self, track_json: dict):
"""
load the track from a dictionary
@ -187,8 +216,7 @@ class Track:
for key, value in track_json.items(): # load all value in the json as class attribute
setattr(self, key, value)
self.file_wu8 = f"{self._track_wu8_dir}/{self.sha1}.wu8"
self.file_szs = f"{self._track_szs_dir}/{self.sha1}.szs"
self.refresh_filename()
return self
@ -200,3 +228,32 @@ class Track:
for k, v in self.__dict__.items():
setattr(new, k, v)
return new
def get_tracks(self): yield self
class TrackGroup(Track):
def __init__(self, tracks: list = None, *args, **kwargs):
super().__init__(*args, **kwargs)
self.tracks = tracks if tracks else []
def load_from_json(self, group_json: dict, *args, **kwargs):
for key, value in group_json.items(): # load all value in the json as class attribute
if key == "group":
for track_json in value:
self.tracks.append(Track(is_in_group=True, *args, **kwargs).load_from_json(track_json))
else:
setattr(self, key, value)
return self
def get_tracks(self):
for track in self.tracks: yield track
def get_ctfile(self, *args, **kwargs):
ctfile_group = f' T T11; T11; 0x02; "-"; "{self.get_track_formatted_name()}"; "-"\n'
for track in self.tracks:
ctfile_group += track.get_ctfile(*args, **kwargs)
return ctfile_group

View file

@ -48,17 +48,16 @@ def sha1(file, autoadd_path: str = "./file/auto-add/") -> str:
@error.better_wszst_error(wszst_tools=WSZST_PATH)
def normalize(src_file: str, dest_dir: str = "./file/Track/", dest_name: str = "%N.szs",
def normalize(src_file: str, dest_file: str = "./file/Track/%N.szs",
output_format: str = "szs", autoadd_path: str = "./file/auto-add/") -> None:
"""
convert a track into an another format
:param src_file: source file
:param dest_dir: destination directory
:param dest_name: destination filename (%N mean same name as src_file)
:param dest_file: destination filename (%N mean same name as src_file)
:param output_format: format of the destination track
:param autoadd_path: path of the auto-add directory
"""
subprocess.run([WSZST_PATH, "NORMALIZE", src_file, "--DEST", dest_dir + dest_name, "--" + output_format,
subprocess.run([WSZST_PATH, "NORMALIZE", src_file, "--DEST", dest_file, "--" + output_format,
"--overwrite", "--autoadd-path", autoadd_path],
creationflags=subprocess.CREATE_NO_WINDOW, stderr=subprocess.PIPE)