import os import glob import subprocess import pandas as pd from datasets import Dataset, Features, Image as HFImage, Value from huggingface_hub import HfApi, DatasetCard import tempfile from PIL import Image # ========================================== # 配置区 # ========================================== REPO_ID = "waveshaker/dataset_collections" REPO_TYPE = "dataset" HF_TOKEN = "" # 请填入你的 Token # ========================================== def clean_txt_newlines(current_dir): """1. 递归清理所有 txt 文件中的换行符""" print("\n" + "="*50) print("🧹 [任务 1/5] 正在清理所有 TXT 文件中的换行符...") print("="*50) txt_files = glob.glob(os.path.join(current_dir, "**", "*.txt"), recursive=True) if not txt_files: print(" ✅ 没有找到需要处理的 TXT 文件。") return cleaned_count = 0 for txt_path in txt_files: # 跳过根目录下的 txt(可能是说明文档) if os.path.dirname(txt_path) == current_dir: continue try: with open(txt_path, 'r', encoding='utf-8') as f: content = ''.join(line.strip() for line in f) with open(txt_path, 'w', encoding='utf-8') as f: f.write(content) cleaned_count += 1 except Exception as e: print(f" ⚠️ 处理 {os.path.basename(txt_path)} 失败: {e}") print(f" 🎉 TXT 清理完成!共处理了 {cleaned_count} 个文件。") def remove_mask_metadata(current_dir): """2. 递归剥离 masklabel.png 的元数据,保护隐私""" print("\n" + "="*50) print("🛡️ [任务 2/5] 正在剥离 Mask 蒙版图 (masklabel.png) 的元数据...") print("="*50) png_files = glob.glob(os.path.join(current_dir, "**", "*.png"), recursive=True) mask_files = [f for f in png_files if "masklabel" in os.path.basename(f).lower()] if not mask_files: print(" ✅ 没有找到 masklabel.png 文件,跳过此步骤。") return success_count = 0 for file_path in mask_files: try: with Image.open(file_path) as img: # 读取像素数据,彻底断开原元数据 data = list(img.getdata()) clean_img = Image.new(img.mode, img.size) clean_img.putdata(data) # 覆盖保存 clean_img.save(file_path, "PNG") success_count += 1 print(f" 🔒 已清理元数据: {os.path.basename(file_path)}") except Exception as e: print(f" ⚠️ 处理失败 [{os.path.basename(file_path)}]: {e}") print(f" 🎉 元数据清理完成!共处理了 {success_count} 张 Mask 图。") def convert_png_to_jpg(current_dir): """3. 批量转换常规 png 为 jpg,并删除原 png(绝对跳过 masklabel)""" print("\n" + "="*50) print("🎬 [任务 3/5] 正在批量转换常规 PNG 为 JPG...") print("="*50) png_files = glob.glob(os.path.join(current_dir, "**", "*.png"), recursive=True) if not png_files: print(" ✅ 没有找到需要转换的 PNG 文件。") return converted_count = 0 for png_path in png_files: # 🚨 核心过滤:跳过 masklabel 文件,保证蒙版依然是 png if "masklabel" in png_path.lower(): continue jpg_path = os.path.splitext(png_path)[0] + ".jpg" try: subprocess.run( ["ffmpeg", "-y", "-i", png_path, "-q:v", "2", jpg_path], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True ) os.remove(png_path) converted_count += 1 print(f" 🔄 转换: {os.path.basename(png_path)} -> {os.path.basename(jpg_path)}") except subprocess.CalledProcessError: print(f" ⚠️ 转换失败: {png_path}") except FileNotFoundError: print(" ❌ 错误: 找不到 ffmpeg!请确保已安装并配置了环境变量。") return print(f" 🎉 转换完成!共转换 {converted_count} 张图片。") def generate_parquet_and_update_yaml(api, current_dir, region_prefix): """4. 本地打包生成 parquet,拉取云端 YAML,追加并重排""" print("\n" + "="*50) print("📦 [任务 4/5] 正在本地打包 Parquet 并更新 YAML 配置...") print("="*50) local_folder_name = os.path.basename(current_dir).replace(" ", "_").replace("-", "_") print(f" ☁️ 正在读取云端 {REPO_ID} 的现有配置...") try: remote_readme_path = api.hf_hub_download(repo_id=REPO_ID, filename="README.md", repo_type=REPO_TYPE) with open(remote_readme_path, "r", encoding="utf-8") as f: content = f.read() card = DatasetCard(content) print(" ✅ 成功读取云端 README。") except Exception as e: print(f" ⚠️ 找不到云端 README,将新建: {e}") card = DatasetCard("") yaml_data = card.data if card.data else {} if "configs" not in yaml_data: yaml_data["configs"] = [] yaml_data["dataset_info"] = { "features": [ {"name": "image", "dtype": "image"}, {"name": "file_name", "dtype": "string"}, {"name": "text", "dtype": "string"} ] } existing_config_names = [c.get("config_name") for c in yaml_data["configs"]] new_configs_added = False valid_folders = [] for sub_folder_name in os.listdir(current_dir): sub_folder_path = os.path.join(current_dir, sub_folder_name) if os.path.isdir(sub_folder_path) and not sub_folder_name.startswith('.'): jpg_files = glob.glob(os.path.join(sub_folder_path, "*.jpg")) + glob.glob(os.path.join(sub_folder_path, "*.jpeg")) if not jpg_files: continue print(f" 📦 正在打包: {sub_folder_name}") data_rows = [] for jpg_path in jpg_files: file_name = os.path.basename(jpg_path) base_name = os.path.splitext(file_name)[0] txt_path = os.path.join(sub_folder_path, f"{base_name}.txt") text_content = "" if os.path.exists(txt_path): with open(txt_path, "r", encoding="utf-8") as f: text_content = f.read().strip() try: with open(jpg_path, "rb") as f: image_bytes = {"bytes": f.read(), "path": None} except Exception as e: print(f" ⚠️ 跳过损坏图片 {file_name}: {e}") continue data_rows.append({ "image": image_bytes, "file_name": file_name, "text": text_content }) if data_rows: df = pd.DataFrame(data_rows) features = Features({ "image": HFImage(), # 👈 这里加上了 HF "file_name": Value("string"), "text": Value("string") }) ds = Dataset.from_pandas(df, features=features) ds.to_parquet(os.path.join(sub_folder_path, "train.parquet")) valid_folders.append(sub_folder_name) print(f" ✅ 生成 train.parquet ({len(data_rows)} 条数据)") config_name = f"{local_folder_name}_{sub_folder_name}".lower() path_str = f"{region_prefix}/{local_folder_name}/{sub_folder_name}/train.parquet" if config_name not in existing_config_names: yaml_data["configs"].append({ "config_name": config_name, "data_files": [{"split": "train", "path": path_str}] }) new_configs_added = True print(f" ➕ 已添加配置: {config_name}") def get_priority(config): data_files = config.get("data_files", []) path = data_files[0].get("path", "") if data_files else "" # 直接使用原始大小写,确保与云端路径完全一致 priority_dict = {"CN": 1, "KR": 2, "JP": 3, "Fictional": 4, "WE": 5} path_prefix = path.split('/')[0] if path else "" return priority_dict.get(path_prefix, 99) yaml_data["configs"] = sorted(yaml_data["configs"], key=lambda c: (get_priority(c), c.get("config_name", ""))) print(" 🔄 已根据 [CN > KR > JP > Fictional > WE] 的顺序重排 YAML。") if new_configs_added: card.data = yaml_data with tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', suffix='.md', delete=False) as temp_readme: temp_readme.write(str(card)) return temp_readme.name, valid_folders return None, valid_folders def upload_files_and_readme(api, current_dir, region_prefix, temp_readme_path, valid_folders): """5. 上传文件及 README""" print("\n" + "="*50) print("🚀 [任务 5/5] 正在上传文件到 Hugging Face...") print("="*50) local_folder_name = os.path.basename(current_dir).replace(" ", "_").replace("-", "_") for sub_folder_name in valid_folders: sub_folder_path = os.path.join(current_dir, sub_folder_name) path_in_repo = f"{region_prefix}/{local_folder_name}/{sub_folder_name}" print(f" 📦 开始上传: {sub_folder_name} -> {path_in_repo}") try: api.upload_folder( folder_path=sub_folder_path, repo_id=REPO_ID, repo_type=REPO_TYPE, path_in_repo=path_in_repo, commit_message=f"Update files for {path_in_repo}", # 上传过滤:保留 masklabel.png,jpg,txt,parquet allow_patterns=["*.jpg", "*.jpeg", "*.txt", "*.png", "*.parquet"] ) print(f" 🎉 {sub_folder_name} 上传完成!") except Exception as e: print(f" ❌ {sub_folder_name} 上传失败: {e}") if temp_readme_path: print("\n 📝 正在推送最新的 README.md...") try: api.upload_file( path_or_fileobj=temp_readme_path, path_in_repo="README.md", repo_id=REPO_ID, repo_type=REPO_TYPE, commit_message="Auto-update README YAML configs" ) print(" 🎉 README.md 已成功更新到云端!") except Exception as e: print(f" ❌ README.md 上传失败: {e}") finally: os.remove(temp_readme_path) def main(): api = HfApi(token=HF_TOKEN) current_dir = os.getcwd() print("\n" + "="*50) print("🌟 欢迎使用 5合1 数据集自动化流水线 🌟") print("="*50) print("请选择该数据集的地区/分类前缀:") print("1. CN | 2. KR | 3. JP | 4. Fictional | 5. WE | 6. 自定义") choice = input("\n👉 请输入数字或直接输入前缀 (默认回车为 KR): ").strip().upper() if not choice: choice = "2" mapping = {"1": "CN", "2": "KR", "3": "JP", "4": "Fictional", "5": "WE"} region_prefix = mapping.get(choice, choice).replace(" ", "_").replace("-", "_") print(f"\n✅ 已选择前缀: {region_prefix}") # 任务 1:清理 txt 换行符 clean_txt_newlines(current_dir) # 任务 2:清理 Mask 元数据 remove_mask_metadata(current_dir) # 任务 3:转换 PNG -> JPG convert_png_to_jpg(current_dir) # 任务 4:打包 Parquet 并更新 YAML temp_readme_path, valid_folders = generate_parquet_and_update_yaml(api, current_dir, region_prefix) # 任务 5:上传 upload_files_and_readme(api, current_dir, region_prefix, temp_readme_path, valid_folders) print("\n" + "="*50) print("🎉 5合1 全流程处理完毕!") print("="*50) if __name__ == "__main__": main()