ABot-World Studio 导出的结果文件 / 用一段脚本先做完整性核对

文章导读
ABot-World Studio 导出结果后,真正容易出问题的不是导出动作本身,而是交付前的核对:目录里到底有多少文件、命名是否一致、有没有零字节文件、视频或图片能不能正常打开。手动逐个点开在小批量时可行,文件一多就会漏。比较稳妥的做法是先在本地写一段 Python 脚本,遍历导出目录,把文件数量、字节大小、命名规则和可读性结果输出成一份清单,再按清单去补。脚本不调用任何平台接口,只读本地文件,
📋 目录
  1. A 先确认导出目录结构和命名规律
  2. B 写脚本遍历目录并统计数量与大小
  3. C 加入可读性检查
  4. D 把异常项汇总成待补清单
  5. E 跑一遍并与人工抽查结果对照
A A

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,能把“这次导出到底有多少文件”固定成可复查的记录。脚本只依赖标准库,不调用未验证接口,放在导出目录同级执行即可。

ABot-World Studio 导出的结果文件 / 用一段脚本先做完整性核对
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 可以直接当作任务列表。

ABot-World Studio 导出的结果文件 / 用一段脚本先做完整性核对
  • 缺失:按预期命名规则推算应有文件,与实际列表比对得出。字段为 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))"

如果不想引入额外依赖,用标准库随机抽样也可以,固定随机种子以便复查。对照时关注两件事:脚本判为正常的文件,人工打开是否也正常;脚本判为异常的文件,人工是否能正常打开。

两者一致时,可以把这份清单作为本次交付的核对记录,随交付物一起留存。出现不一致时不要急着改结论,先定位原因:脚本判正常但人工打不开,多半是本地库能读而播放器或业务端不认,需要记录具体打开方式;脚本判异常但人工能打开,多半是库版本、编码或容器格式支持差异,应在脚本里补充说明而不是直接删掉这条记录。调整判定逻辑后重新跑一遍,再抽样对照,直到两边结论稳定一致,才把清单交给负责补文件的人。