65 lines
3.3 KiB
Python
65 lines
3.3 KiB
Python
"""Step 8: build the EML mod into the repository (mod/<id>: the result that is committed).
|
|
|
|
usage: mod then `python install.py` copies it into the game and dry-runs it
|
|
The mod replaces each note's EN sound (the Russian slot does not exist in VoiceoverResource: its fixed
|
|
languages are EN/DE/FR/ES/ZH): SoundResource.streamedData points to the new QOA content, frameCount and
|
|
duration follow it, VoiceoverResource.durations[EN] too (10 ms units). The game files are not touched:
|
|
EML (dbghelp.dll proxy) patches enshrouded.kfc at launch and keeps enshrouded.kfc.bak (emm restore).
|
|
"""
|
|
import json
|
|
import pathlib
|
|
import shutil
|
|
|
|
from . import __version__
|
|
from .config import ROOT, load
|
|
|
|
LUA = '''local lines = require("lines")
|
|
local ok, fail = 0, 0
|
|
for _, l in ipairs(lines) do
|
|
local done, err = pcall(function()
|
|
local snd = game.assets.get_resource(l.sound, "keen::SoundResource", 0)
|
|
local d = snd.data
|
|
local content = game.assets.create_content(io.read("data/" .. l.sound .. ".qoa"))
|
|
d.streamedData.hash = content
|
|
d.frameCount = l.frames
|
|
d.framesPerSecond = 48000
|
|
d.duration.value = math.floor(l.seconds * 1e9)
|
|
local vo = game.assets.get_resource(l.vo, "keen::VoiceoverResource", 0)
|
|
vo.data.durations[1] = math.floor(l.seconds * 100)
|
|
end)
|
|
if done then ok = ok + 1 else fail = fail + 1; print("[ru_voice] FAIL " .. l.name .. ": " .. tostring(err)) end
|
|
end
|
|
print(("[ru_voice] replaced %d notes, %d failed"):format(ok, fail))
|
|
'''
|
|
|
|
|
|
def main():
|
|
cfg = load()
|
|
notes = json.load(open(cfg.w("notes.json"), encoding="utf-8"))
|
|
metas = [json.load(open(cfg.w("qoa", f"{n['k']}.json"))) for n in notes if cfg.w("qoa", f"{n['k']}.json").exists()]
|
|
if len(metas) < len(notes):
|
|
print(f"WARNING: {len(metas)} of {len(notes)} notes are assembled; the others stay English")
|
|
mod = pathlib.Path(cfg.get("mod", "dir") or ROOT / "mod" / cfg.get("mod", "id", "ru_voice_notes"))
|
|
if mod.exists():
|
|
shutil.rmtree(mod)
|
|
(mod / "src").mkdir(parents=True)
|
|
(mod / "data").mkdir()
|
|
for m in metas:
|
|
shutil.copy(cfg.w("qoa", f"{m['sound']}.qoa"), mod / "data" / f"{m['sound']}.qoa")
|
|
lines = ["-- generated by ensvoice: one entry per voiced note", "return {"]
|
|
lines += [f' {{ sound = "{m["sound"]}", vo = "{m["vo"]}", frames = {m["frames"]}, seconds = {m["seconds"]:.6f}, '
|
|
f'name = "{m["name"]}" }},' for m in metas]
|
|
(mod / "src" / "lines.lua").write_text("\n".join(lines + ["}"]) + "\n", encoding="utf-8")
|
|
(mod / "src" / "mod.lua").write_text(LUA, encoding="utf-8")
|
|
json.dump(dict(id=cfg.get("mod", "id", "ru_voice_notes"), name=cfg.get("mod", "name", "RU voice: notes"),
|
|
version=cfg.get("mod", "version", __version__), capabilities=["patch", "export"],
|
|
description="Russian narration for the voiced notes (Qwen3-TTS ru-stress, narrator clone, "
|
|
"automatic QC). Replaces the English voiceover slot.",
|
|
authors=cfg.get("mod", "authors", [])), open(mod / "mod.json", "w"), indent=2)
|
|
mb = sum(f.stat().st_size for f in (mod / "data").iterdir()) / 2 ** 20
|
|
print("mod", mod, len(metas), "notes", round(mb, 1), "MB; install: python install.py")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|