Russian narration for the Enshrouded notes: mod 0.4.0, installer, generation pipeline
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user