ABot-World Studio 导出结果后,真正容易出问题的不是导出动作本身,而是交付前的核对:目录里到底有多少文件、命名是否一致、有没有零字节文件、视频或图片能不能正常打开。手动逐个点开在小批量时可行,文件一多就会漏。比较稳妥的做法是先在本地写一段 Python 脚本,遍历导出目录,把文件数量、字节大小、命名规则和可读性结果输出成一份清单,再按清单去补。脚本不调用任何平台接口,只读本地文件,因此可以先在副本目录上跑,确认结论可靠后再用于正式交付。
适用场景:ABot-World Studio 导出结果需要打包交付、且文件数量已经超过手动检查可承受范围时。操作动作:用 pathlib 遍历导出目录,统计文件数与字节数、按命名规则分类、用图像或视频库尝试打开,把缺失、零字节、打不开三类问题写成待补清单。验证方式:随机抽若干文件人工比对脚本结论,一致则采信,不一致则先修脚本判定逻辑。风险边界:脚本只能证明“文件存在且能被本地库打开”,不能替代对内容正确性和业务含义的确认;目录结构和命名规则需要结合自己的导出配置确认。
先确认导出目录结构和命名规律
脚本要判断“缺没缺”,前提是知道“应该有什么”。所以第一步不是写遍历,而是把导出目录的真实结构打印出来,看清层级、文件类型和命名规律。ABot-World Studio 不同导出选项产出的目录层级可能有差异,建议先跑一段只读的目录树打印,把结果贴到记录里,再据此写规则。
import os
from pathlib import Path
root = Path("export_output") # 换成自己的导出目录
for dirpath, dirnames, filenames in os.walk(root):
depth = len(Path(dirpath).relative_to(root).parts)
print(" " * depth + Path(dirpath).name + "/")
for name in sorted(filenames):
p = Path(dirpath) / name
print(" " * (depth + 1) + f"{name}\t{p.stat().st_size} bytes")
一份典型的输出样例大致长这样,用于说明结构而不是给出固定数字:
export_output/
images/
scene_001_frame_0001.png 204800 bytes
scene_001_frame_0002.png 0 bytes
videos/
scene_001.mp4 10485760 bytes
meta/
scene_001.json 1024 bytes
从这份样例里能读出三件事:文件按类型分目录;命名由场景名加帧号或序号构成;存在零字节文件。把命名规律写成可执行的判断条件,比如用正则提取场景名与序号,用扩展名区分图片、视频、元数据。规则先写成常量放在脚本顶部,导出配置变了只改常量,不改遍历逻辑。
写脚本遍历目录并统计数量与大小
人工点数的问题在于不可重复,换个时间再点一遍结论可能不同。用 pathlib 遍历目录并写出 CSV,能把“这次导出到底有多少文件”固定成可复查的记录。脚本只依赖标准库,不调用未验证接口,放在导出目录同级执行即可。
import csv
from pathlib import Path
ROOT = Path("export_output")
OUT = Path("manifest.csv")
EXTS = {".png", ".jpg", ".jpeg", ".mp4", ".json", ".txt"}
rows = []
total_bytes = 0
for p in sorted(ROOT.rglob("*")):
if not p.is_file():
continue
size = p.stat().st_size
total_bytes += size
rows.append({
"relative_path": str(p.relative_to(ROOT)),
"suffix": p.suffix.lower(),
"size_bytes": size,
"known_type": p.suffix.lower() in EXTS,
})
with OUT.open("w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=["relative_path", "suffix", "size_bytes", "known_type"])
writer.writeheader()
writer.writerows(rows)
print(f"files={len(rows)} bytes={total_bytes}")
print(f"unknown_suffix={sum(1 for r in rows if not r['known_type'])}")
运行命令与预期输出:
python check_export.py
# files=13 bytes=12697600
# unknown_suffix=0
这里的数字只是示意,需要以自己的实际导出为准。判断方式是:先跑两次,确认 files 与 bytes 完全一致;再把 manifest.csv 的行数与系统文件管理器里显示的条目数对一遍。两次结果不一致,说明遍历期间目录被改动过,应重新导出或重新跑。
加入可读性检查
文件存在且大小正常,不代表能打开。零字节文件可以在上一步直接被筛出来,但截断的文件只有尝试打开才会暴露。做法是用图像或视频库实际读一次,捕获异常并记录异常类型与文件名。图片可用 Pillow,视频可用 OpenCV;哪个库装不上就跳过该类型,不要用不存在的接口硬编。
from pathlib import Path
from PIL import Image
import cv2
failures = []
for p in sorted(Path("export_output").rglob("*")):
if not p.is_file():
continue
suffix = p.suffix.lower()
try:
if suffix in {".png", ".jpg", ".jpeg"}:
with Image.open(p) as im:
im.verify()
elif suffix == ".mp4":
cap = cv2.VideoCapture(str(p))
ok = cap.isOpened()
frame_ok, _ = cap.read() if ok else (False, None)
cap.release()
if not ok or not frame_ok:
raise ValueError("video cannot be opened or has no readable frame")
except Exception as exc:
failures.append({
"relative_path": str(p.relative_to("export_output")),
"size_bytes": p.stat().st_size,
"error_type": type(exc).__name__,
"error_msg": str(exc)[:120],
})
for f in failures:
print(f)
print(f"failed={len(failures)}")
需要注意,Image.verify() 只检查图片文件头和结构完整性,不会解码全部像素;视频读取第一帧也只是一个保守的最小检查。这套检查的作用是把明显损坏的文件挡在交付前,不能证明画面内容符合预期。open 失败但人工能打开的文件,通常是库版本或编码支持问题,需要结合环境确认,不要直接判定为导出失败。
把异常项汇总成待补清单
检查结果如果只是打印在终端里,很难派给具体人去补。建议把问题分成三类分别输出,每类带固定字段和排序规则,导出的 CSV 可以直接当作任务列表。
- 缺失:按预期命名规则推算应有文件,与实际列表比对得出。字段为 expected_path、scene、sequence、reason;按 scene、sequence 升序排,方便同一场景一次性补齐。
- 零字节:字段为 relative_path、suffix、parent_dir、detected_at;按 parent_dir、relative_path 排,便于按目录批量重导。
- 打不开:字段为 relative_path、suffix、size_bytes、error_type、error_msg;先按 error_type 分组,再按 size_bytes 升序排,通常小文件异常更可能是截断,可作为优先排查对象。
三类清单建议保持同一份 relative_path 口径,全部使用相对导出根目录的路径,避免不同机器上绝对路径不一致导致无法定位。清单产出后先人工过一遍数量级,如果某类异常数量远大于预期,更可能是规则写错了,而不是导出真的坏了,此时应回到第一步核对目录结构和命名规律。
跑一遍并与人工抽查结果对照
脚本结论只有在与人工结果对上之后才值得采信。做法是从 manifest.csv 里随机抽取若干条,覆盖图片、视频、元数据三种类型,逐个在本地打开确认;同时从异常清单里抽几条复核,确认它们确实打不开或确实缺失。
python -c "import pandas as pd; print(pd.read_csv('manifest.csv').sample(n=5, random_state=7))"
如果不想引入额外依赖,用标准库随机抽样也可以,固定随机种子以便复查。对照时关注两件事:脚本判为正常的文件,人工打开是否也正常;脚本判为异常的文件,人工是否能正常打开。
两者一致时,可以把这份清单作为本次交付的核对记录,随交付物一起留存。出现不一致时不要急着改结论,先定位原因:脚本判正常但人工打不开,多半是本地库能读而播放器或业务端不认,需要记录具体打开方式;脚本判异常但人工能打开,多半是库版本、编码或容器格式支持差异,应在脚本里补充说明而不是直接删掉这条记录。调整判定逻辑后重新跑一遍,再抽样对照,直到两边结论稳定一致,才把清单交给负责补文件的人。