# -*- coding: utf-8 -*-
"""原创离线合成样例；不连接模型、不读取账户。输出位于脚本旁。
已有输入保留，修改输入后可以重跑。失败边界由 --negative 单独演示。
"""
from pathlib import Path
import sys, json, csv, math, io, hashlib, html, base64, re
sys.stdout.reconfigure(encoding='utf-8')
sys.stderr.reconfigure(encoding='utf-8')
BASE = Path(__file__).resolve().parent
NEG = '--negative' in sys.argv
def write(name, value):
    p=BASE/name; p.parent.mkdir(parents=True,exist_ok=True)
    if isinstance(value,bytes): p.write_bytes(value)
    else: p.write_text(value,encoding='utf-8',newline='')
def seed(name,value):
    if not (BASE/name).exists(): write(name,value)
def jwrite(name,value): write(name,json.dumps(value,ensure_ascii=False,indent=2))
def jseed(name,value):
    if not (BASE/name).exists(): jwrite(name,value)
def load(name): return json.loads((BASE/name).read_text(encoding='utf-8'))
def table(name,heads,rows):
    f=io.StringIO(newline='');w=csv.writer(f);w.writerow(heads);w.writerows(rows);write(name,f.getvalue())
def page(name,title,body):
    write(name,'<!doctype html><html lang="zh-CN"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>'+html.escape(title)+'</title><style>body{max-width:1000px;margin:2rem auto;padding:0 1rem;font:17px/1.7 system-ui}table{border-collapse:collapse}td,th{border:1px solid #aaa;padding:.5rem}pre{white-space:pre-wrap;overflow-wrap:anywhere}img,svg{max-width:100%;height:auto}a{color:#125caa}</style><h1>'+html.escape(title)+'</h1>'+body+'</html>')
def emit(data): print(json.dumps(data,ensure_ascii=False))

import wave,struct
rate=8000;n=rate*2
if not (BASE/'synthetic.wav').exists():
    samples=[int(12000*math.sin(2*math.pi*440*i/rate)) for i in range(n)]
    with wave.open(str(BASE/'synthetic.wav'),'wb') as w:w.setparams((1,2,rate,0,'NONE','not compressed'));w.writeframes(struct.pack('<'+'h'*n,*samples))
jseed('word-times.json',[{'id':'w1','start':.5,'end':.8,'redact':True},{'id':'w2','start':.78,'end':1.0,'redact':True}]);words=load('word-times.json')
if NEG:words[0]['end']=words[0]['start']-.1
with wave.open(str(BASE/'synthetic.wav'),'rb') as w:params=w.getparams();raw=w.readframes(w.getnframes())
if params.nchannels!=1 or params.sampwidth!=2:raise ValueError('只处理16-bit单声道PCM')
rate=params.framerate
intervals=[]
for r in words:
    if not r['redact']:continue
    if r['end']<=r['start']:raise ValueError('词时间逆序')
    a=max(0,math.floor((r['start']-.05)*rate));b=min(params.nframes,math.ceil((r['end']+.05)*rate))
    if a<b:intervals.append((a,b))
merged=[]
for a,b in sorted(intervals):
    if merged and a<=merged[-1][1]:merged[-1][1]=max(b,merged[-1][1])
    else:merged.append([a,b])
red=bytearray(raw)
for a,b in merged:red[a*2:b*2]=bytes((b-a)*2)
with wave.open(str(BASE/'redacted.wav'),'wb') as w:w.setparams(params);w.writeframes(red)
table('sample-ranges.csv',['first_sample','end_exclusive'],merged);emit({'ranges':merged,'frames':params.nframes,'source_sha256':hashlib.sha256(raw).hexdigest(),'tone_only':True})
