chenchaoyun commited on
Commit
90383e4
·
1 Parent(s): 71eaf61
test/remove_duplicate_celeb_images.py ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ 遍历指定目录,根据文件内容(MD5)查找重复项,如果发现重复则只保留一个。
4
+ 默认目标目录为 /opt/data/chinese_celeb_dataset,可用 --target-dir 覆盖。
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import argparse
10
+ import hashlib
11
+ import os
12
+ import sys
13
+ from pathlib import Path
14
+ from typing import Dict
15
+
16
+ DEFAULT_TARGET_DIR = Path("/opt/data/chinese_celeb_dataset")
17
+ CHUNK_SIZE = 4 * 1024 * 1024 # 4MB
18
+
19
+
20
+ def compute_md5(file_path: Path) -> str:
21
+ """流式计算文件 MD5,避免一次性读入大文件。"""
22
+ digest = hashlib.md5()
23
+ with file_path.open("rb") as fh:
24
+ for chunk in iter(lambda: fh.read(CHUNK_SIZE), b""):
25
+ digest.update(chunk)
26
+ return digest.hexdigest()
27
+
28
+
29
+ def deduplicate(target_dir: Path, dry_run: bool = False) -> int:
30
+ """执行去重逻辑,返回删除的重复文件数量。"""
31
+ if not target_dir.exists():
32
+ print(f"[error] 目标目录不存在: {target_dir}", file=sys.stderr)
33
+ return 0
34
+ if not target_dir.is_dir():
35
+ print(f"[error] 目标路径不是目录: {target_dir}", file=sys.stderr)
36
+ return 0
37
+
38
+ md5_map: Dict[str, Path] = {}
39
+ removed = 0
40
+ scanned = 0
41
+
42
+ # 按路径排序,确保始终保留最先遍历到的文件
43
+ for file_path in sorted(target_dir.rglob("*")):
44
+ if not file_path.is_file() or file_path.is_symlink():
45
+ continue
46
+
47
+ scanned += 1
48
+ try:
49
+ file_md5 = compute_md5(file_path)
50
+ except Exception as exc:
51
+ print(f"[warn] 计算 MD5 失败: {file_path} -> {exc}", file=sys.stderr)
52
+ continue
53
+
54
+ original = md5_map.get(file_md5)
55
+ if original is None:
56
+ md5_map[file_md5] = file_path
57
+ continue
58
+
59
+ if dry_run:
60
+ print(f"[dry-run] {file_path} 与 {original} 内容相同,将被删除")
61
+ else:
62
+ try:
63
+ os.remove(file_path)
64
+ removed += 1
65
+ print(f"[remove] 删除重复文件: {file_path} (原始: {original})")
66
+ except Exception as exc:
67
+ print(f"[error] 删除失败: {file_path} -> {exc}", file=sys.stderr)
68
+
69
+ print(
70
+ f"[summary] 扫描文件: {scanned}, 保留唯一文件: {len(md5_map)}, 删除重复文件: {removed}{' (dry-run)' if dry_run else ''}"
71
+ )
72
+ return removed
73
+
74
+
75
+ def parse_args() -> argparse.Namespace:
76
+ parser = argparse.ArgumentParser(description="按 MD5 删除重复文件,仅保留一个副本。")
77
+ parser.add_argument(
78
+ "--target-dir",
79
+ type=Path,
80
+ default=DEFAULT_TARGET_DIR,
81
+ help=f"需要去重的目录(默认: {DEFAULT_TARGET_DIR})",
82
+ )
83
+ parser.add_argument(
84
+ "--dry-run",
85
+ action="store_true",
86
+ help="只输出将删除的文件,不实际删除。",
87
+ )
88
+ return parser.parse_args()
89
+
90
+
91
+ def main() -> int:
92
+ args = parse_args()
93
+ target_dir = args.target_dir.expanduser().resolve()
94
+ deduplicate(target_dir, dry_run=args.dry_run)
95
+ return 0
96
+
97
+
98
+ if __name__ == "__main__":
99
+ raise SystemExit(main())