""" package_exporter.py — 达人素材交付包生成 架构方案§五 1A步骤5:按笔记分文件夹 + 图(01/02/03) + 文案.txt + 发布清单 + 合规说明 路径规则:{UPLOAD_ABS_ROOT}/packages/{workspace_id}/{task_id}/note_{n}/ """ from __future__ import annotations import json import logging import os import zipfile from datetime import datetime from pathlib import Path from typing import Any logger = logging.getLogger(__name__) # 文件夹结构 # uploads/packages/{workspace_id}/{task_id}/ # note_01/ # 01_hook.jpg # 按 seq 序号命名防传错序 # 02_proof.jpg # 文案.txt # 标题 + 正文 + 标签 # note_02/ # ... # 📋发布清单.txt # ✅合规说明.txt # package.zip # 最终打包文件 def build_delivery_package( workspace_id: int, task_id: int, notes: list[dict], # 每条笔记,含 text_candidate + image_candidates base_path: str = "uploads/packages", ) -> str: """ 打包交付,返回 zip 文件的本地路径。 notes 格式:[{ "title": str, "content": str, "tags": list[str], "images": [{"seq": int, "role": str, "data": bytes}], "banned_word_status": str, # 合规说明用 }] """ package_dir = Path(base_path) / str(workspace_id) / str(task_id) package_dir.mkdir(parents=True, exist_ok=True) note_dirs: list[Path] = [] for idx, note in enumerate(notes, start=1): note_dir = package_dir / f"note_{idx:02d}" note_dir.mkdir(exist_ok=True) note_dirs.append(note_dir) # ── 图片文件(按 seq 序号命名) for img in sorted(note.get("images", []), key=lambda x: x.get("seq", 0)): seq = img.get("seq", idx) role = img.get("role", "img") fname = f"{seq:02d}_{role}.jpg" img_data = img.get("data", b"") if img_data: (note_dir / fname).write_bytes(img_data) # ── 文案.txt(标题 + 正文 + 标签,达人可直接复制) tags = note.get("tags") or [] body = note.get("content", "") # 正文末尾如果 LLM 已写入 #话题 标签,不再重复追加(避免重复) body_has_tags = bool(tags) and any( t.strip("#") in body for t in tags if t ) copy_lines = [ f"【标题】{note.get('title', '')}", "", body, ] if tags and not body_has_tags: copy_lines += ["", " ".join(tags)] (note_dir / "文案.txt").write_text("\n".join(copy_lines), encoding="utf-8") # ── 发布清单.txt checklist_lines = [ "📋 发布清单", f"生成时间:{datetime.now().strftime('%Y-%m-%d %H:%M')}", f"任务ID:{task_id}", "", ] for idx, note in enumerate(notes, start=1): title = note.get("title", f"笔记{idx}") n_images = len(note.get("images", [])) checklist_lines.append(f"note_{idx:02d} 标题:{title[:30]} 图片数:{n_images}") checklist_lines += [ "", "发布注意事项:", "- 每条笔记图片按 01/02/03 顺序上传,避免传错序", "- 文案.txt 中标题/正文/标签已区分,复制对应部分", "- 品牌词已植入,请勿删除", "- 不要添加链接(种品牌词,引导天猫搜索成交)", ] (package_dir / "📋发布清单.txt").write_text("\n".join(checklist_lines), encoding="utf-8") # ── 合规说明.txt compliance_lines = [ "✅ 合规说明", f"生成时间:{datetime.now().strftime('%Y-%m-%d %H:%M')}", "", "本批次内容已完成以下合规处理:", "1. 违禁词扫描(美白/祛斑/速效/医用/药妆等)", "2. 视觉违禁词处理(前后对比/变白等)", "3. 图片去水印处理(C2PA元数据已清除)", "", "各笔记合规状态:", ] for idx, note in enumerate(notes, start=1): status = note.get("banned_word_status", "pass") status_label = {"pass": "✅通过", "auto_fixed": "✅自动改写", "soft_warn": "⚠️软提示", "hard_block": "❌硬拦截"}.get(status, status) compliance_lines.append(f"note_{idx:02d}:{status_label}") compliance_lines += [ "", "注:C2PA元数据可去除;私有像素水印只能削弱,不保证100%清除。", "如有合规疑问,请联系运营团队。", ] (package_dir / "✅合规说明.txt").write_text("\n".join(compliance_lines), encoding="utf-8") # ── 打 zip zip_path = package_dir / "package.zip" with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf: for note_dir in note_dirs: for fpath in sorted(note_dir.iterdir()): zf.write(fpath, arcname=f"{note_dir.name}/{fpath.name}") zf.write(package_dir / "📋发布清单.txt", arcname="📋发布清单.txt") zf.write(package_dir / "✅合规说明.txt", arcname="✅合规说明.txt") logger.info("delivery package built: %s (notes=%d)", zip_path, len(notes)) return str(zip_path)