# -*- coding: utf-8 -*- """把一份/多份视频清单切成分片, 上传到 COS 的 todo/, 供常驻 worker 认领。 用法: python tools/seed_jobs.py 清单1.txt [清单2.txt ...] python tools/seed_jobs.py ./text2 # 目录: 取其中所有 *.txt python tools/seed_jobs.py ./text2 --skip-processed # 灌之前先过滤掉全局已处理过的视频 分片大小取 config.dispatch.shard_size(默认2000)。分片名: shard_<起始序号>。 幂等性: 同名分片已存在(todo 里有)则跳过, 不覆盖。 --skip-processed: 聚合 30 台机器各自最新一天的 数据.jsonl 快照(dispatch.daily_backup_path), 合并成全局已处理视频集合再过滤清单 —— 避免同一视频被不同机器重复处理(本机 数据.jsonl 只能 挡住"这台机器"重复跑, 挡不住跨机器重复)。有最长约1天(daily_backup_hour)的数据延迟。 """ import argparse import glob import os import sys sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from config.config_utils import config # noqa: E402 from tool import job_cos # noqa: E402 def _collect_lines(paths): lines = [] for p in paths: files = glob.glob(os.path.join(p, "*.txt")) if os.path.isdir(p) else [p] for fp in files: with open(fp, encoding="utf-8") as f: lines += [ln.strip() for ln in f if ln.strip()] # 去重并保序 seen, out = set(), [] for ln in lines: if ln not in seen: seen.add(ln) out.append(ln) return out def main(): ap = argparse.ArgumentParser(description="切片灌任务到 COS todo/") ap.add_argument("paths", nargs="+", help="清单.txt 或目录(取其中所有 *.txt)") ap.add_argument("--skip-processed", action="store_true", help="灌之前先过滤掉全局(所有机器)已处理过的视频,见文件头说明") ap.add_argument("--all-dates", action="store_true", help="配合 --skip-processed: 不只取每台机器最新一天,把所有历史日期的已处理清单都拉下来合并判断" "(机群已停机、想更保险地核对时用;正常灌任务不需要,只会多下载很多文件)") args = ap.parse_args() shard_size = int((config.get_dispatch_config() or {}).get("shard_size", 2000)) lines = _collect_lines(args.paths) if not lines: print("✗ 没有读到任何视频路径") sys.exit(1) if args.skip_processed: if args.all_dates: print("正在聚合所有机器【全部历史日期】的已处理清单(daily_backup_path)...") else: print("正在聚合各机器最新一天的已处理清单(daily_backup_path)...") processed = job_cos.global_processed_set(verbose=True, all_dates=args.all_dates) before = len(lines) lines = [ln for ln in lines if ln not in processed] print(f"全局已处理 {len(processed)} 条 -> 过滤掉 {before - len(lines)} 条,剩余 {len(lines)} 条待灌。") if not lines: print("✓ 全部已处理过,无需灌任务。") return existing = set(job_cos.list_todo()) n_shards = (len(lines) + shard_size - 1) // shard_size print(f"共 {len(lines)} 条 -> {n_shards} 个分片(每片≤{shard_size})。已存在 {len(existing)} 个分片。") added = skipped = 0 for i in range(0, len(lines), shard_size): shard = f"shard_{i:08d}" if shard in existing: skipped += 1 continue body = "\n".join(lines[i:i + shard_size]).encode("utf-8") job_cos._put(job_cos._key("todo", shard), body) # 普通写(任务清单, 不需禁止覆盖) added += 1 if added % 50 == 0: print(f" 已上传 {added} 个分片...") print(f"✓ 完成: 新增 {added} 个分片, 跳过已存在 {skipped} 个。worker 会自动认领。") if __name__ == "__main__": main()