#!/usr/bin/env python3 # -*- coding: utf-8 -*- """Prepare and assemble iter006 data from iter005 Chinese assets plus new English assets.""" from __future__ import annotations import argparse import json import shutil from pathlib import Path from typing import Any, Dict, Iterable, List, Set DEFAULT_ITER005 = Path("/workspace/echoloc/Dataset/Novel/query_data/supervisor_iterations/iter_005") DEFAULT_ITER006 = Path("/workspace/echoloc/Dataset/Novel/query_data/supervisor_iterations/iter_006") def read_jsonl(path: Path) -> List[Dict[str, Any]]: rows: List[Dict[str, Any]] = [] if not path.exists(): return rows with path.open("r", encoding="utf-8") as handle: for line in handle: line = line.strip() if line: try: rows.append(json.loads(line)) except json.JSONDecodeError: rows.append(json.loads(line.lstrip("\ufeff;;"))) return rows def write_jsonl(path: Path, rows: Iterable[Dict[str, Any]]) -> int: path.parent.mkdir(parents=True, exist_ok=True) n = 0 with path.open("w", encoding="utf-8") as handle: for row in rows: handle.write(json.dumps(row, ensure_ascii=False) + "\n") n += 1 return n def qid(row: Dict[str, Any]) -> str: return str(row.get("qid") or row.get("id") or "") def is_vstyle_en(row: Dict[str, Any]) -> bool: cur = qid(row) if cur.startswith("vstyle_en_"): return True language = str(row.get("language") or ((row.get("source") or {}).get("language")) or "").lower() return language == "en" and cur.startswith("vstyle_") def is_vstyle_zh(row: Dict[str, Any]) -> bool: cur = qid(row) if cur.startswith("vstyle_en_"): return False if cur.startswith("vstyle_zh_"): return True language = str(row.get("language") or ((row.get("source") or {}).get("language")) or "").lower() return language == "zh" and cur.startswith("vstyle_") def is_story_zh(row: Dict[str, Any]) -> bool: cur = qid(row) if cur.startswith("vstyle_") or cur.startswith("story_en_"): return False if "vstyle_en_" in cur: return False language = str(row.get("language") or ((row.get("source") or {}).get("language")) or "zh").lower() return language == "zh" def is_zh_reuse(row: Dict[str, Any]) -> bool: return is_story_zh(row) or is_vstyle_zh(row) def mark_language(row: Dict[str, Any], language: str) -> Dict[str, Any]: row = dict(row) row["language"] = language source = row.get("source") if isinstance(source, dict): source = dict(source) source["language"] = language row["source"] = source source_candidate = row.get("source_query_candidate") if isinstance(source_candidate, dict): source_candidate = dict(source_candidate) source_candidate["language"] = language row["source_query_candidate"] = source_candidate return row def by_qid(rows: Iterable[Dict[str, Any]]) -> Dict[str, Dict[str, Any]]: out: Dict[str, Dict[str, Any]] = {} for row in rows: cur = qid(row) if cur: out[cur] = row return out def read_first_jsonl(paths: Iterable[Path]) -> List[Dict[str, Any]]: for path in paths: rows = read_jsonl(path) if rows: return rows return [] def copy_if_exists(src: Path, dst: Path) -> bool: if not src.exists(): return False dst.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(src, dst) return True def extract_vstyle_en(iter005: Path, iter006: Path) -> Dict[str, Any]: out_dir = iter006 / "english_rebuild" / "vstyle_en" out_dir.mkdir(parents=True, exist_ok=True) candidates = [mark_language(row, "en") for row in read_jsonl(iter005 / "filtered/query_candidates_keep.jsonl") if is_vstyle_en(row)] controls = [mark_language(row, "en") for row in read_jsonl(iter005 / "filtered/query_tts_qwen3_input_structured.jsonl") if is_vstyle_en(row)] eval_rows = [dict(row, language="en") for row in read_jsonl(iter005 / "eval_inputs/e2e_input_aligned.jsonl") if is_vstyle_en(row)] counts = { "vstyle_en_candidates": write_jsonl(out_dir / "query_candidates.jsonl", candidates), "vstyle_en_candidates_keep": write_jsonl(out_dir / "filtered/query_candidates_keep.jsonl", candidates), "vstyle_en_query_tts_controls": write_jsonl(out_dir / "filtered/query_tts_qwen3_input_structured.jsonl", controls), "vstyle_en_existing_eval_rows": write_jsonl(out_dir / "existing_eval_audio_rows.jsonl", eval_rows), } (out_dir / "README_iter006_vstyle_en.txt").write_text( "Use filtered/query_candidates_keep.jsonl as RESPONSE_IN for English response/thinker target regeneration. " "existing_eval_audio_rows.jsonl preserves iter005 query_audio_path for vstyle_en.\n", encoding="utf-8", ) return {"out_dir": str(out_dir), "counts": counts} def copy_zh_reuse(iter005: Path, iter006: Path) -> Dict[str, Any]: out_dir = iter006 / "zh_reuse_from_iter005" out_dir.mkdir(parents=True, exist_ok=True) specs = [ ("query_candidates.jsonl", "query_candidates.jsonl"), ("filtered/query_candidates_keep.jsonl", "filtered/query_candidates_keep.jsonl"), ("filtered/query_tts_qwen3_input_structured.jsonl", "filtered/query_tts_qwen3_input_structured.jsonl"), ("thinker_targets/response_controls.jsonl", "thinker_targets/response_controls.jsonl"), ("thinker_targets/thinker_targets.jsonl", "thinker_targets/thinker_targets.jsonl"), ("eval_inputs/e2e_input_aligned.jsonl", "eval_inputs/e2e_input_aligned.jsonl"), ("eval_inputs/talker_input_all.jsonl", "eval_inputs/talker_input_all.jsonl"), ("eval_inputs/response_tts_input_aligned.jsonl", "eval_inputs/response_tts_input_aligned.jsonl"), ] counts: Dict[str, Any] = {} for rel_src, rel_dst in specs: rows = [row for row in read_jsonl(iter005 / rel_src) if is_zh_reuse(row)] counts[rel_dst] = write_jsonl(out_dir / rel_dst, rows) for rel in ["sft/thinker_emochange/train.jsonl", "sft/thinker_emochange/val.jsonl", "sft/thinker_emochange/manifest.json"]: counts[rel] = "not_copied_final_sft_rebuilt_from_merged_eval" return {"out_dir": str(out_dir), "counts": counts} def merge_unique(parts: Iterable[Iterable[Dict[str, Any]]]) -> List[Dict[str, Any]]: out: Dict[str, Dict[str, Any]] = {} for rows in parts: for row in rows: cur = qid(row) if cur: out[cur] = row return [out[key] for key in sorted(out)] def merge_eval_with_existing_audio(new_eval_rows: List[Dict[str, Any]], existing_audio_rows: List[Dict[str, Any]]) -> List[Dict[str, Any]]: audio_by_qid = by_qid(existing_audio_rows) merged = [] for row in new_eval_rows: cur = qid(row) audio = audio_by_qid.get(cur) if audio: row = dict(row) row["query_audio_path"] = audio.get("query_audio_path", row.get("query_audio_path", "")) row["query_audio_control_path"] = audio.get("query_audio_control_path", row.get("query_audio_control_path", "")) merged.append(row) return merged def merge_final(iter005: Path, iter006: Path, story_en_root: Path, vstyle_en_root: Path) -> Dict[str, Any]: zh_root = iter006 / "zh_reuse_from_iter005" final_eval = iter006 / "eval_inputs" final_targets = iter006 / "thinker_targets" final_filtered = iter006 / "filtered" if not zh_root.exists(): copy_zh_reuse(iter005, iter006) zh_candidates = read_jsonl(zh_root / "query_candidates.jsonl") zh_keep = read_jsonl(zh_root / "filtered/query_candidates_keep.jsonl") zh_controls = read_jsonl(zh_root / "filtered/query_tts_qwen3_input_structured.jsonl") zh_responses = read_jsonl(zh_root / "thinker_targets/response_controls.jsonl") zh_targets = read_jsonl(zh_root / "thinker_targets/thinker_targets.jsonl") zh_e2e = read_jsonl(zh_root / "eval_inputs/e2e_input_aligned.jsonl") zh_talker = read_jsonl(zh_root / "eval_inputs/talker_input_all.jsonl") zh_response_aligned = read_jsonl(zh_root / "eval_inputs/response_tts_input_aligned.jsonl") story_candidates = read_jsonl(story_en_root / "query_candidates.jsonl") story_keep = read_jsonl(story_en_root / "filtered/query_candidates_keep.jsonl") story_controls = read_jsonl(story_en_root / "filtered/query_tts_qwen3_input_structured.jsonl") story_responses = read_jsonl(story_en_root / "thinker_targets/response_controls.jsonl") story_targets = read_jsonl(story_en_root / "thinker_targets/thinker_targets.jsonl") story_e2e = read_jsonl(story_en_root / "eval_inputs/e2e_input_aligned.jsonl") story_talker = read_jsonl(story_en_root / "eval_inputs/talker_input_all.jsonl") story_response_aligned = read_jsonl(story_en_root / "eval_inputs/response_tts_input_aligned.jsonl") vstyle_candidates = read_first_jsonl([vstyle_en_root / "filtered/query_candidates_keep.jsonl", vstyle_en_root / "query_candidates_keep.jsonl"]) vstyle_controls = read_first_jsonl([vstyle_en_root / "filtered/query_tts_qwen3_input_structured.jsonl", vstyle_en_root / "query_tts_qwen3_input_structured.jsonl"]) vstyle_responses = read_jsonl(vstyle_en_root / "thinker_targets/response_controls.jsonl") vstyle_targets = read_jsonl(vstyle_en_root / "thinker_targets/thinker_targets.jsonl") vstyle_export = read_jsonl(vstyle_en_root / "eval_inputs/e2e_input_aligned.jsonl") vstyle_existing_audio = read_jsonl(vstyle_en_root / "existing_eval_audio_rows.jsonl") if not vstyle_export and vstyle_responses and vstyle_targets and vstyle_existing_audio: response_by_qid = by_qid(vstyle_responses) audio_by_qid = by_qid(vstyle_existing_audio) vstyle_export = [] for target in vstyle_targets: cur = qid(target) response = response_by_qid.get(cur, {}) audio = audio_by_qid.get(cur, {}) style = str((target.get("combined") or {}).get("instruct") or "").strip() text = str((target.get("combined") or {}).get("txt") or "").strip() visible_query = response.get("visible_query") or (response.get("source_query_candidate") or {}).get("visible_query") or target.get("source_query") or {} vstyle_export.append( { "id": cur, "qid": cur, "query_type": target.get("query_type") or response.get("query_type"), "language": "en", "query_text": visible_query.get("text", audio.get("query_text", "")) if isinstance(visible_query, dict) else "", "query_audio_path": audio.get("query_audio_path", ""), "query_audio_control_path": audio.get("query_audio_control_path", ""), "oracle_thinker_style": style, "oracle_thinker_text": text, "oracle_response_text": response.get("audio_content", text.replace(" <|EMO_CHANGE|> ", " ")), "response_control": response.get("final_generated_control") or response.get("response_generated_control"), "source_event": response.get("source_event"), } ) vstyle_e2e = merge_eval_with_existing_audio(vstyle_export, vstyle_existing_audio) vstyle_talker = read_jsonl(vstyle_en_root / "eval_inputs/talker_input_all.jsonl") vstyle_response_aligned = read_jsonl(vstyle_en_root / "eval_inputs/response_tts_input_aligned.jsonl") if not vstyle_talker and vstyle_targets: vstyle_talker = [] for target in vstyle_targets: cur = qid(target) combined = target.get("combined") or {} vstyle_talker.append( { "id": cur, "qid": cur, "language": "en", "ability": "constructed_thinker_to_talker", "query_type": target.get("query_type"), "thinker_style": str(combined.get("instruct") or "").strip(), "thinker_text": str(combined.get("txt") or "").strip(), "need_emochange": target.get("need_emochange"), "source_query": target.get("source_query"), "aligned_with_query_audio": cur in {qid(row) for row in vstyle_e2e}, } ) if not vstyle_response_aligned and vstyle_responses and vstyle_e2e: e2e_qids = {qid(row) for row in vstyle_e2e} vstyle_response_aligned = [row for row in vstyle_responses if qid(row) in e2e_qids] counts = { "query_candidates": write_jsonl(iter006 / "query_candidates.jsonl", merge_unique([zh_candidates, story_candidates, vstyle_candidates])), "query_candidates_keep": write_jsonl(final_filtered / "query_candidates_keep.jsonl", merge_unique([zh_keep, story_keep, vstyle_candidates])), "query_tts_controls": write_jsonl(final_filtered / "query_tts_qwen3_input_structured.jsonl", merge_unique([zh_controls, story_controls, vstyle_controls])), "response_controls": write_jsonl(final_targets / "response_controls.jsonl", merge_unique([zh_responses, story_responses, vstyle_responses])), "thinker_targets": write_jsonl(final_targets / "thinker_targets.jsonl", merge_unique([zh_targets, story_targets, vstyle_targets])), "e2e_input_aligned": write_jsonl(final_eval / "e2e_input_aligned.jsonl", merge_unique([zh_e2e, story_e2e, vstyle_e2e])), "talker_input_all": write_jsonl(final_eval / "talker_input_all.jsonl", merge_unique([zh_talker, story_talker, vstyle_talker])), "response_tts_input_aligned": write_jsonl(final_eval / "response_tts_input_aligned.jsonl", merge_unique([zh_response_aligned, story_response_aligned, vstyle_response_aligned])), } (iter006 / "iter006_merge_summary.json").write_text(json.dumps(counts, ensure_ascii=False, indent=2), encoding="utf-8") return {"iter006": str(iter006), "counts": counts} def counts(iter006: Path) -> Dict[str, Any]: paths = [ "query_candidates.jsonl", "filtered/query_candidates_keep.jsonl", "filtered/query_tts_qwen3_input_structured.jsonl", "thinker_targets/response_controls.jsonl", "thinker_targets/thinker_targets.jsonl", "eval_inputs/e2e_input_aligned.jsonl", "eval_inputs/talker_input_all.jsonl", "eval_inputs/response_tts_input_aligned.jsonl", "sft/thinker_emochange/train.jsonl", "sft/thinker_emochange/val.jsonl", ] return {rel: len(read_jsonl(iter006 / rel)) for rel in paths} def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("mode", choices=["copy-zh", "extract-vstyle-en", "merge-final", "counts"]) parser.add_argument("--iter005", type=Path, default=DEFAULT_ITER005) parser.add_argument("--iter006", type=Path, default=DEFAULT_ITER006) parser.add_argument("--story_en_root", type=Path, default=DEFAULT_ITER006 / "english_rebuild/story_en") parser.add_argument("--vstyle_en_root", type=Path, default=DEFAULT_ITER006 / "english_rebuild/vstyle_en") args = parser.parse_args() if args.mode == "copy-zh": result = copy_zh_reuse(args.iter005, args.iter006) elif args.mode == "extract-vstyle-en": result = extract_vstyle_en(args.iter005, args.iter006) elif args.mode == "merge-final": result = merge_final(args.iter005, args.iter006, args.story_en_root, args.vstyle_en_root) else: result = {"iter006": str(args.iter006), "counts": counts(args.iter006)} print(json.dumps(result, ensure_ascii=False, indent=2)) return 0 if __name__ == "__main__": raise SystemExit(main())