| import json |
| import os |
| import numpy as np |
| from tqdm import tqdm |
| from concurrent.futures import ProcessPoolExecutor, as_completed |
| import decord |
| from decord import VideoReader, cpu |
|
|
| |
| |
| import logging |
| decord.logging.set_level(logging.ERROR) |
|
|
| |
| class DataArgs: |
| def __init__(self): |
| self.video_fps = 1 |
| self.frames_upbound = 16 |
| self.force_sample = True |
|
|
| data_args = DataArgs() |
|
|
| def check_single_sample(item): |
| """ |
| 核心验证逻辑:完全复刻训练时的视频抽帧代码 |
| 只有通过了真实 get_batch 考验的视频,才算真正的“好数据” |
| """ |
| video_path = item.get("video_abs_path") |
| if not video_path or not os.path.exists(video_path): |
| return False, item, "文件不存在或路径为空" |
|
|
| try: |
| |
| vr = VideoReader(video_path, ctx=cpu(0), num_threads=1) |
| total_frame_num = len(vr) |
| |
| if total_frame_num == 0: |
| return False, item, "视频总帧数为 0" |
| |
| avg_fps = round(vr.get_avg_fps() / data_args.video_fps) |
| if avg_fps <= 0: |
| avg_fps = 1 |
| |
| frame_idx = [i for i in range(0, total_frame_num, avg_fps)] |
| |
| if data_args.frames_upbound > 0: |
| if len(frame_idx) > data_args.frames_upbound or data_args.force_sample: |
| uniform_sampled_frames = np.linspace(0, total_frame_num - 1, data_args.frames_upbound, dtype=int) |
| frame_idx = uniform_sampled_frames.tolist() |
| |
| |
| video = vr.get_batch(frame_idx).asnumpy() |
| |
| if video.shape[0] == 0: |
| return False, item, "提取到的帧矩阵为空" |
| |
| return True, item, "OK" |
| except Exception as e: |
| |
| return False, item, f"解码崩溃: {str(e)}" |
|
|
| def run_deep_clean(input_json, clean_json, bad_json, num_workers=16): |
| print(f"正在读取数据集: {input_json} ...") |
| with open(input_json, 'r') as f: |
| data_list = json.load(f) |
| |
| |
|
|
| clean_data = [] |
| bad_data = [] |
| |
| print(f"\n🚀 开始深度清洗 {len(data_list)} 条数据 (启用 {num_workers} 个进程并发)...") |
| |
| |
| with ProcessPoolExecutor(max_workers=num_workers) as executor: |
| futures = [executor.submit(check_single_sample, item) for item in data_list] |
| |
| for future in tqdm(as_completed(futures), total=len(data_list), desc="🔨 质检进度"): |
| is_valid, item, error_msg = future.result() |
| if is_valid: |
| clean_data.append(item) |
| else: |
| item['error_reason'] = error_msg |
| bad_data.append(item) |
| |
| print("\n" + "="*50) |
| print(f"✅ 清洗彻底完成!") |
| print(f"🎉 纯净数据: {len(clean_data)} 条 -> 已保存至 {clean_json}") |
| print(f"☠️ 损毁数据: {len(bad_data)} 条 -> 已保存至 {bad_json}") |
| print("="*50) |
| |
| with open(clean_json, 'w') as f: |
| json.dump(clean_data, f, indent=4) |
| with open(bad_json, 'w') as f: |
| json.dump(bad_data, f, indent=4) |
|
|
| if __name__ == "__main__": |
| |
| INPUT_FILE = "./data/video_reversal_sft_train_cleaned_10k.json" |
| CLEAN_FILE = "./data/video_reversal_sft_train_ultra_clean.json" |
| BAD_FILE = "./data/video_reversal_sft_train_bad_samples.json" |
| |
| |
| run_deep_clean(INPUT_FILE, CLEAN_FILE, BAD_FILE, num_workers=16) |