-
Notifications
You must be signed in to change notification settings - Fork 57
/
generate.py
executable file
·323 lines (272 loc) · 11.2 KB
/
generate.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
#!/usr/bin/env python3
import git
import json
import re
import urllib.parse
import yaml
from bannergif import bannergif
from datetime import datetime
from glob import glob
from os import listdir, makedirs, mkdir, path, system
from PIL import Image
from py7zr import SevenZipFile
SKIN_FOLDER_REGEX = r"(.*?\/?)(?:theme.ini|background|battery|grf|quickmenu|ui|video|volume|(?:bckgrd_1|bckgrd_2|icons|logo)\.png|(?:large|small)(?:-ds|-dsi)?\.nftr|uisettings\.ini)$"
def webName(name: str) -> str:
"""Convert names to lowercase alphanumeric + underscore and hyphen"""
name = name.lower()
out = ""
for letter in name:
if letter in "abcdefghijklmnopqrstuvwxyz0123456789-_":
out += letter
elif letter in ". ":
out += "-"
return out
def getTheme(path: str) -> int:
"""Gets the theme of a skin based on its path"""
if "3dsmenu/" in path:
return "Nintendo 3DS"
elif "akmenu/" in path:
return "Wood UI"
elif "dsimenu/" in path:
return "Nintendo DSi"
elif "r4menu/" in path:
return "R4 Original"
elif "extras/fonts/" in path:
return "Font"
elif "icons/" in path:
return "Icon"
elif "unlaunch/" in path:
return "Unlaunch"
return ""
def getDefaultIcon(path: str) -> int:
"""Gets the default icon of a skin based on its path"""
if "3dsmenu/" in path:
return 0
elif "dsimenu/" in path:
return 1
elif "r4menu/" in path:
return 2
elif "akmenu/" in path:
return 3
elif "extras/fonts/" in path:
return 4
elif "icons/" in path:
return 5
return -1
def lastUpdated(sevenZip):
"""Gets the latest date from the items in a 7z"""
latest = None
for item in sevenZip.list():
if latest is None or item.creationtime > latest:
latest = item.creationtime
return latest
def downloadScript(skin: str, folder: str) -> list:
"""Makes a script to download the specified skin"""
skinName = skin[skin.rfind("/") + 1:skin.rfind(".")]
if skin.endswith(".7z"):
return [
{
"type": "downloadFile",
"file": "https://raw.githubusercontent.com/DS-Homebrew/twlmenu-extras/master/" + urllib.parse.quote(skin),
"output": f"/{skinName}.7z"
},
{
"type": "extractFile",
"file": f"/{skinName}.7z",
"input": folder,
"output": f"/{skin[:-3]}/"
},
{
"type": "deleteFile",
"file": f"/{skinName}.7z"
}
]
else:
return [
{
"type": "downloadFile",
"file": "https://raw.githubusercontent.com/DS-Homebrew/twlmenu-extras/master/" + urllib.parse.quote(skin),
"output": "/" + skin
}
]
# Read version from old unistore
unistoreOld = {}
if path.exists(path.join("unistore", "twlmenu-skins.unistore")):
with open(path.join("unistore", "twlmenu-skins.unistore"), "r", encoding="utf8") as file:
unistoreOld = json.load(file)
# Output JSON
output = []
# Create UniStore base
unistore = {
"storeInfo": {
"title": "TWiLight Menu++ Skins",
"author": "DS-Homebrew",
"url": "https://raw.githubusercontent.com/DS-Homebrew/twlmenu-extras/master/unistore/twlmenu-skins.unistore",
"file": "twlmenu-skins.unistore",
"sheetURL": "https://raw.githubusercontent.com/DS-Homebrew/twlmenu-extras/master/unistore/twlmenu-skins.t3x",
"sheet": "twlmenu-skins.t3x",
"description": "A collection of skins for TWiLight Menu++\nfrom DS-Homebrew/twlmenu-extras on GitHub\n\n(The 'Console' is the theme in TWiLight)",
"version": 3,
"revision": 0 if ("storeInfo" not in unistoreOld or "revision" not in unistoreOld["storeInfo"]) else unistoreOld["storeInfo"]["revision"]
},
"storeContent": [],
}
# Icons array
icons = []
iconIndex = 0
# Make 3DS, DSi, R4 icons
for file in ["3ds.png", "dsi.png", "r4.png", "ak.png", "font.png"]:
with Image.open(open(path.join("unistore", "icons", file), "rb")) as icon:
if not path.exists(path.join("unistore", "temp")):
mkdir(path.join("unistore", "temp"))
icon.thumbnail((48, 48))
icon.save(path.join("unistore", "temp", str(iconIndex) + ".png"))
icons.append(str(iconIndex) + ".png")
iconIndex += 1
# Get skin files
files = [f for f in glob("_nds/TWiLightMenu/*menu/themes/*.7z")]
files += [f for f in glob("_nds/TWiLightMenu/extras/fonts/*.7z")]
files += [f for f in glob("_nds/TWiLightMenu/icons/*.png")]
files += [f for f in glob("_nds/TWiLightMenu/icons/*.bin")]
files += [f for f in glob("_nds/TWiLightMenu/unlaunch/backgrounds/*.gif")]
# Generate UniStore entries
for skin in files:
print(skin)
info = {}
updated = datetime.utcfromtimestamp(0)
folder = ""
skinName = skin[skin.rfind("/") + 1:skin.rfind(".")]
if skin[-2:] == "7z":
with SevenZipFile(skin) as a:
updated = lastUpdated(a)
folder, = next((re.findall(SKIN_FOLDER_REGEX, x.filename) for x in a.files if len(re.findall(SKIN_FOLDER_REGEX, x.filename)) > 0), ("",))
else:
updated = datetime.utcfromtimestamp(int(git.Repo(".").git.log(["-n1", "--pretty=format:%ct", "--", skin]) or 0))
created = datetime.utcfromtimestamp(int(git.Repo(".").git.log(["--pretty=format:%ct", "--", skin]).split("\n")[-1] or 0))
if path.exists(path.join(skin[:skin.rfind("/")], "meta", skinName, "info.json")):
with open(path.join(skin[:skin.rfind("/")], "meta", skinName, "info.json")) as file:
info = json.load(file)
elif path.exists(path.join(skin[:skin.rfind("/")], "_meta.json")):
with open(path.join(skin[:skin.rfind("/")], "_meta.json")) as file:
j = json.load(file)
if skinName in j:
info = j[skinName]
screenshots = []
titles = None
if path.exists(path.join(skin[:skin.rfind("/")], "meta", skinName, "screenshots")):
dirlist = listdir((path.join(skin[:skin.rfind("/")], "meta", skinName, "screenshots")))
dirlist.sort()
for screenshot in dirlist:
if screenshot[-3:] == "png":
screenshots.append({
"url": "https://raw.githubusercontent.com/DS-Homebrew/twlmenu-extras/master/" + skin[:skin.rfind("/")] + "/meta/" + urllib.parse.quote(skinName) + "/screenshots/" + screenshot,
"description": screenshot[:screenshot.rfind(".")].capitalize().replace("-", " ")
})
elif skin[-3:] in ("gif", "png"): # Unlaunch bg or icon
screenshots.append({
"url": "https://raw.githubusercontent.com/DS-Homebrew/twlmenu-extras/master/" + skin,
"description": skinName.capitalize().replace("-", " ")
})
elif skin[-3:] == "bin": # banner.bin icon
if not path.exists(path.join(skin[:skin.rfind("/")], "gif")):
mkdir(path.join(skin[:skin.rfind("/")], "gif"))
gifPath = path.join(skin[:skin.rfind("/")], "gif", skinName + ".gif")
with open(skin, "rb") as f:
titles = bannergif(f, gifPath) # convert to GIF and get titles
screenshots.append({
"url": "https://raw.githubusercontent.com/DS-Homebrew/twlmenu-extras/master/" + gifPath,
"description": skinName.capitalize().replace("-", " ")
})
skinInfo = {
"title": info["title"] if "title" in info else skinName,
"version": info["version"] if "version" in info else "v1.0.0",
"author": info["author"] if "author" in info else "",
"category": info["categories"] if "categories" in info else [],
"console": getTheme(skin),
"icon_index": getDefaultIcon(skin),
"description": info["description"] if "description" in info else "",
"screenshots": screenshots,
"license": info["license"] if "license" in info else "",
"last_updated": updated.strftime("%Y-%m-%d at %H:%M (UTC)")
}
color = None
if ("unistore_exclude" not in info or info["unistore_exclude"] is False) and (getTheme(skin) != "Unlaunch"):
# Make icon for UniStore
if not path.exists(path.join("unistore", "temp")):
mkdir(path.join("unistore", "temp"))
iconPath = None
if path.exists(path.join(skin[:skin.rfind("/")], "meta", skinName, "icon.png")):
iconPath = path.join(skin[:skin.rfind("/")], "meta", skinName, "icon.png")
elif skin[-3:] == "png":
iconPath = skin
elif skin[-3:] == "bin":
iconPath = path.join(skin[:skin.rfind("/")], "gif", skinName + ".gif")
if iconPath:
with Image.open(iconPath) as icon:
if skin[-3:] not in ("png", "bin"):
icon.thumbnail((48, 48))
icon.save(path.join("unistore", "temp", str(iconIndex) + ".png"))
icons.append(str(iconIndex) + ".png")
skinInfo["icon_index"] = iconIndex
iconIndex += 1
color = icon.copy().convert("RGB")
color.thumbnail((1, 1))
color = color.getpixel((0, 0))
color = f"#{color[0]:02x}{color[1]:02x}{color[2]:02x}"
# Add entry to UniStore
unistore["storeContent"].append({
"info": skinInfo,
info["title"] if "title" in info else skinName: downloadScript(skin, folder)
})
# Website file
web = skinInfo.copy()
web["layout"] = "app"
web["created"] = created.strftime("%Y-%m-%dT%H:%M:%SZ")
web["updated"] = updated.strftime("%Y-%m-%dT%H:%M:%SZ")
web["systems"] = [web["console"]]
if color:
web["color"] = color
if titles:
web["titles"] = titles
web["downloads"] = {skin[skin.rfind("/") + 1:]: {
"url": "https://raw.githubusercontent.com/DS-Homebrew/twlmenu-extras/master/" + skin,
"size": path.getsize(skin)
}}
if skin[-3:] in ("gif", "png"):
web["icon"] = "https://raw.githubusercontent.com/DS-Homebrew/twlmenu-extras/master/" + skin
elif skin[-3:] == "bin":
web["icon"] = "https://raw.githubusercontent.com/DS-Homebrew/twlmenu-extras/master/" + path.join(skin[:skin.rfind("/")], "gif", skinName + ".gif")
elif web["icon_index"] < 4:
web["icon"] = "https://raw.githubusercontent.com/DS-Homebrew/twlmenu-extras/master/unistore/icons/" + ["3ds", "dsi", "r4", "ak"][web["icon_index"]] + ".png"
else:
web["icon"] = "https://raw.githubusercontent.com/DS-Homebrew/twlmenu-extras/master/" + skin[:skin.rfind("/")] + "/meta/" + urllib.parse.quote(skinName) + "/icon.png"
web["image"] = web["icon"]
web.pop("icon_index")
if "title" in web:
if not path.exists(path.join("docs", "_" + webName(web["console"]))):
mkdir(path.join("docs", "_" + webName(web["console"])))
with open(path.join("docs", "_" + webName(web["console"]), webName(web["title"]).lower() + ".md"), "w", encoding="utf8") as file:
file.write("---\n" + yaml.dump(web, allow_unicode=True) + "---\n")
for category in web["category"]:
if not path.exists(path.join("docs", webName(web["console"]), "category")):
makedirs(path.join("docs", webName(web["console"]), "category"))
with open(path.join("docs", webName(web["console"]), "category", category.lower() + ".md"), "w", encoding="utf8") as file:
file.write(f"---\nlayout: cards\ntitle: {getTheme(skin)} - {category}\nsystem: {webName(web['console'])}\ncategory: {category}\n---\n<div class=\"alert alert-secondary mb-4\"><span class=\"i18n innerHTML-category\">Category: </span><span class=\"i18n innerHTML-cat-{category}\">{category}</span></div>\n")
output.append(web)
# Make t3x
with open(path.join("unistore", "temp", "icons.t3s"), "w", encoding="utf8") as file:
file.write("--atlas -f rgba -z auto\n\n")
for icon in icons:
file.write(icon + "\n")
system("tex3ds -i " + path.join("unistore", "temp", "icons.t3s") + " -o " + path.join("unistore", "twlmenu-skins.t3x"))
# Increment revision if not the same
if unistore != unistoreOld:
unistore["storeInfo"]["revision"] += 1
# Write unistore to file
with open(path.join("unistore", "twlmenu-skins.unistore"), "w", encoding="utf8") as file:
file.write(json.dumps(unistore, sort_keys=True, ensure_ascii=False))
# Write output file
if not path.exists(path.join("docs", "data")):
makedirs(path.join("docs", "data"))
with open(path.join("docs", "data", "full.json"), "w", encoding="utf8") as file:
file.write(json.dumps(output, sort_keys=True, ensure_ascii=False))