Russian narration for the Enshrouded notes: mod 0.4.0, installer, generation pipeline
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
"""Step 6: pick the best take of every segment (work/pick.json, work/pick_report.json).
|
||||
|
||||
A take passes when it has no error, a pace of 7-19 chars/s and
|
||||
- segments of up to 3 words (headings, names, signatures): CER <= 0.35 (ASR mangles lone names);
|
||||
- longer ones: WER <= 0.12 or a single wrong word.
|
||||
Among passing takes: score = 6*stress + 0.5*MOS + 2*SIM - 3*WER (stress first: a wrong stress is what
|
||||
a listener hears). No passing take: the lowest WER, and the segment is listed as weak — `render --weak`
|
||||
speaks it in voice.extra_seeds, then qc and pick again.
|
||||
"""
|
||||
import collections
|
||||
import glob
|
||||
import json
|
||||
import re
|
||||
|
||||
import jiwer
|
||||
|
||||
from .config import load
|
||||
|
||||
|
||||
def norm(t):
|
||||
return " ".join(re.sub(r"[^\w\s]", " ", t.lower().replace("ё", "е")).split())
|
||||
|
||||
|
||||
def passes(s, r):
|
||||
if "error" in r or not 7 <= r["cps"] <= 19:
|
||||
return False
|
||||
if len(s["text"].split()) <= 3:
|
||||
h = norm(r.get("hyp", ""))
|
||||
return bool(h) and jiwer.cer(norm(s["text"]), h) <= 0.35
|
||||
return r["wer"] <= 0.12 or r["wer"] * len(norm(s["text"]).split()) <= 1.01
|
||||
|
||||
|
||||
def score(r):
|
||||
return 6 * r["stress_ok"] / max(1, r["stress_n"]) + 0.5 * r.get("mos", 3) + 2 * r["sim"] - 3 * r["wer"]
|
||||
|
||||
|
||||
def main():
|
||||
cfg = load()
|
||||
segs = json.load(open(cfg.w("segs.json"), encoding="utf-8"))
|
||||
qc = {}
|
||||
for f in sorted(glob.glob(str(cfg.w("qc", "x").parent / "*.json"))):
|
||||
qc.update(json.load(open(f, encoding="utf-8")))
|
||||
by = collections.defaultdict(dict)
|
||||
for stem, r in qc.items():
|
||||
take, seed = stem.rsplit("_s", 1)
|
||||
by[take][int(seed)] = r
|
||||
pick, weak, missing = {}, [], []
|
||||
for s in segs:
|
||||
takes = by.get(s["take"], {})
|
||||
if not takes:
|
||||
missing.append(s["id"]); continue
|
||||
ok = {sd: r for sd, r in takes.items() if passes(s, r)}
|
||||
if ok:
|
||||
pick[s["id"]] = max(ok, key=lambda sd: score(ok[sd]))
|
||||
else:
|
||||
pick[s["id"]] = min(takes, key=lambda sd: takes[sd]["wer"])
|
||||
weak.append(s["id"])
|
||||
json.dump(pick, open(cfg.w("pick.json"), "w"))
|
||||
take_of = {s["id"]: s["take"] for s in segs}
|
||||
P = [by[take_of[i]][sd] for i, sd in pick.items()]
|
||||
rep = dict(segments=len(segs), picked=len(pick), weak=weak, missing=missing,
|
||||
wer=round(sum(r["wer"] for r in P) / max(1, len(P)), 4),
|
||||
mos=round(sum(r.get("mos", 0) for r in P) / max(1, len(P)), 3),
|
||||
sim=round(sum(r["sim"] for r in P) / max(1, len(P)), 3),
|
||||
stress=round(sum(r["stress_ok"] for r in P) / max(1, sum(r["stress_n"] for r in P)), 4),
|
||||
seeds=dict(collections.Counter(pick.values())))
|
||||
json.dump(rep, open(cfg.w("pick_report.json"), "w"), indent=1)
|
||||
print({k: (len(v) if isinstance(v, list) else v) for k, v in rep.items()})
|
||||
if missing:
|
||||
print(f"{len(missing)} segments have no scored take: run render and qc first")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user