"""Recover per-turn time boundaries for every viewer scenario from silence gaps in the merged mp3 (turns were concatenated with ~0.35-0.5 s of silence). Writes data/viewer/turn_bounds.json: { sid: {"n_turns": N, "bounds": [t1, t2, ...] (N-1 seconds, midpoints of gaps), "method": "silence"|"text"} } Fallback when the gap count doesn't match: split proportionally to turn text length. """ import json, subprocess, re, sys, glob, os from concurrent.futures import ThreadPoolExecutor V = "data/viewer" def probe(sid, turns): mp3 = f"{V}/{sid}_dataset.mp3" if not os.path.isfile(mp3): return sid, None try: dur = float(subprocess.run(["ffprobe","-v","error","-show_entries","format=duration","-of","csv=p=0",mp3],capture_output=True,text=True).stdout.strip()) out = subprocess.run(["ffmpeg","-hide_banner","-i",mp3,"-af","silencedetect=noise=-35dB:d=0.25","-f","null","-"],capture_output=True,text=True).stderr except Exception as e: return sid, None starts = [float(x) for x in re.findall(r"silence_start: ([0-9.]+)", out)] ends = [float(x) for x in re.findall(r"silence_end: ([0-9.]+)", out)] gaps = [(s, e) for s, e in zip(starts, ends) if s > 0.15 and e < dur - 0.15] n = len(turns) if n <= 1: return sid, {"n_turns": n, "bounds": [], "duration": dur, "method": "single"} if len(gaps) >= n - 1: # take the (n-1) longest gaps, in time order pick = sorted(sorted(gaps, key=lambda g: g[1]-g[0], reverse=True)[:n-1]) return sid, {"n_turns": n, "bounds": [round((s+e)/2, 3) for s, e in pick], "duration": dur, "method": "silence"} lens = [max(1, len(t.get("text",""))) for t in turns]; tot = sum(lens); acc = 0; b = [] for L in lens[:-1]: acc += L; b.append(round(dur * acc / tot, 3)) return sid, {"n_turns": n, "bounds": b, "duration": dur, "method": "text"} jobs = [] for f in glob.glob(f"{V}/*_pred_v18m_dataset.json"): sid = os.path.basename(f).replace("_pred_v18m_dataset.json", "") try: turns = json.load(open(f)).get("turns", []) except Exception: continue jobs.append((sid, turns)) res = {} with ThreadPoolExecutor(8) as ex: for sid, r in ex.map(lambda j: probe(*j), jobs): if r: res[sid] = r json.dump(res, open(f"{V}/turn_bounds.json", "w"), ensure_ascii=False) from collections import Counter print(len(res), "scenarios;", Counter(r["method"] for r in res.values()))