1. Seedance 2.0 分段视频为什么必须写脚本合并
Seedance 2.0 在本地推理时,受显存和序列长度限制,长视频任务会被拆成 2s、4s 的碎片 MP4 输出。你拿到手的目录通常长这样:0001.mp4、0002.mp4、0010.mp4,文件名看着有序,但一旦超过 9 段,字符串排序就会把10.mp4排到2.mp4前面,拼出来的视频直接时序错乱。更麻烦的是,推理失败的碎片会留下 0KB 空文件或无法解码的坏片,手动拖进剪映不仅耗时,还会触发二次压缩,把 Seedance 原本干净的画质糊掉一层。
我试过用 moviepy 处理一批 30 段的素材,跑到第 20 段内存直接飙到 8G 然后崩掉;也手写过 ffmpeg concat 命令,光是生成filelist.txt和转码对齐参数就写了一屏,换一批素材还得重来。所以这个场景真正需要的不是"能拼",而是"批量、无损、自动排序、自动跳过坏片"。
这篇要做的,就是用 openClaw 驱动一个 Python 脚本,把 Seedance 2.0 输出目录里的碎片视频自动整理、排序、校验、无损拼接成一条完整长视频。适合本地部署 Seedance 的创作者、做 AI 视频工作流的开发者,以及需要批量出片的工作室。全程只需要改两个路径参数,其余逻辑脚本自己跑。
核心检索词先明确:openClaw 是一个面向多媒体自动化的轻量 Python 工具库,主打无损视频拼接、帧对齐、时序排序和坏片过滤;Seedance 2.0 是分段输出的文生视频模型;我们要做的是用 Python 脚本把这两者串起来,实现视频拼接自动化。
下面从目录结构开始,一步步给出可复制的配置和命令。
2. openClaw 环境准备与 Seedance 输出目录规范
在写脚本之前,先把环境和一个干净的目录结构定下来。openClaw 对 Python 版本的要求是 3.8 到 3.11,和 Seedance 官方推理环境基本一致,所以不需要额外建虚拟环境,直接在现有环境里装依赖即可。
安装命令如下:
pip install openclaw pip install ffmpeg-python numpyffmpeg-python 是 openClaw 底层调用编码器时的依赖,numpy 用于帧对齐时的数组运算。装完之后可以用一条命令验证:
python -c "import openclaw; print(openclaw.__version__)"能打印出版本号就说明库加载正常。如果报ModuleNotFoundError,大概率是 pip 装到了另一个 Python 解释器下,用python -m pip install openclaw重装一次。
接下来是目录规范。Seedance 2.0 默认会把碎片视频吐在一个输出目录里,我建议你手动整理成下面这种结构,脚本会更好写,也方便后面做批量任务:
seedance_workspace/ ├── task_001/ │ ├── 0001.mp4 │ ├── 0002.mp4 │ ├── 0003.mp4 │ └── ... ├── task_002/ │ ├── 0001.mp4 │ └── ... └── merged_output/ └── (脚本生成的完整视频放这里)每个task_xxx是一个独立的生成任务,里面的碎片按 Seedance 的序号命名规则排列。merged_output用来存放合并后的成品,和原始素材分开,避免脚本遍历时把成品又当成碎片读进去。
这里有个坑要提前说:路径里不要出现中文、空格和特殊符号。openClaw 底层调用 ffmpeg 时,带空格的路径如果没有正确转义,会直接报No such file or directory。我踩过一次,目录名是"我的视频 01",排查了半小时才发现是空格问题。全英文加下划线是最稳的。
如果你想把脚本接到 TaoToken 的模型服务上做后续处理,可以先把 API Key 准备好。TaoToken 的 API 地址是https://taotoken.net/api,Key 在控制台生成,接入文档里有完整的调用示例。这一步不是必须的,但如果你打算把合并后的视频再走一遍模型做字幕或摘要,提前配好会省事。
环境就绪后,进入脚本编写环节。
3. 可复制的 openClaw 视频拼接脚本与参数配置
这一节给出完整脚本,你可以直接复制运行,只需要改开头两个路径常量。脚本分三块:读取排序、坏片过滤、无损合并。
先看核心参数配置,我把它单独抽出来,方便你对照修改:
# ====================== 核心参数配置 ====================== SEEDANCE_OUTPUT_DIR = "./seedance_workspace/task_001" # 碎片视频目录 SAVE_OUTPUT_PATH = "./seedance_workspace/merged_output/task_001_full.mp4" VIDEO_SUFFIX = ".mp4" MIN_FILE_SIZE = 1024 # 小于 1KB 视为坏片,直接跳过 SORT_MODE = "numeric" # numeric 按数字序号排序,避免 10 < 2 LOSSLESS = True # 无损拼接,不二次压缩 AUTO_FIX = True # 自动修复首尾帧断层与音画偏移 # ========================================================MIN_FILE_SIZE这个参数很关键。Seedance 推理失败时会留下 0KB 或几百字节的空壳文件,如果不过滤,ffmpeg 读到会直接中断整个合并流程。设成 1024 字节是个经验值,正常 2s 的 720p 碎片至少也有几十 KB。
下面是完整脚本:
""" openClaw 自动合并 Seedance 2.0 分段视频 功能:遍历目录、数字排序、过滤坏片、无损拼接、输出完整长视频 """ import os import openclaw SEEDANCE_OUTPUT_DIR = "./seedance_workspace/task_001" SAVE_OUTPUT_PATH = "./seedance_workspace/merged_output/task_001_full.mp4" VIDEO_SUFFIX = ".mp4" MIN_FILE_SIZE = 1024 LOSSLESS = True AUTO_FIX = True def get_sorted_segments(dir_path): """读取碎片视频并按数字序号排序,过滤坏片""" segments = [] if not os.path.isdir(dir_path): raise FileNotFoundError(f"目录不存在: {dir_path}") for name in os.listdir(dir_path): if not name.endswith(VIDEO_SUFFIX): continue full_path = os.path.join(dir_path, name) if os.path.getsize(full_path) < MIN_FILE_SIZE: print(f"[跳过坏片] {name} 体积过小") continue segments.append(full_path) # 数字序号排序,解决 10.mp4 排在 2.mp4 前面的问题 segments.sort(key=lambda p: int(os.path.splitext(os.path.basename(p))[0])) return segments def merge_seedance_video(): segments = get_sorted_segments(SEEDANCE_OUTPUT_DIR) if not segments: print("未检测到有效碎片视频,请检查目录与命名规则") return print(f"读取到 {len(segments)} 段碎片,开始无损合并...") merger = openclaw.VideoMerger( segment_list=segments, auto_fix=AUTO_FIX, lossless=LOSSLESS, ) merger.merge(output_path=SAVE_OUTPUT_PATH) print(f"合并完成: {SAVE_OUTPUT_PATH}") if __name__ == "__main__": merge_seedance_video()排序那一行是整个脚本的心脏。int(os.path.splitext(...)[0])把文件名里的数字部分强制转成整数再比较,0001和1都能正确解析,10也不会再排到2前面。如果你的 Seedance 输出命名带前缀,比如frame_0001.mp4,把split(".")[0]换成正则提取数字即可:
import re segments.sort(key=lambda p: int(re.search(r"(\d+)", os.path.basename(p)).group(1)))VideoMerger的两个参数含义要记牢:auto_fix=True会在拼接前对每段做首尾帧对齐,修复 Seedance 分段推理时偶尔出现的黑屏和音画偏移;lossless=True走的是流复制模式,不重新编码,画质和码率完全保留。这两个都建议开着,除非你的碎片本身编码格式不统一,那才需要关掉 lossless 做一次统一转码。
如果你要把这个脚本接到 TaoToken 的 Coding Plan 里做长期批量任务,可以在合并完成后加一段调用,把成品路径回传。Coding Plan 适合这种需要反复跑的自动化流程,比单次调用省心。
脚本写好了,下一步是跑起来看结果。
4. 运行脚本验证合并结果与 ffmpeg 校验
把脚本保存为merge_seedance.py,在终端里执行:
python merge_seedance.py正常输出会是这样:
[跳过坏片] 0007.mp4 体积过小 读取到 12 段碎片,开始无损合并... 合并完成: ./seedance_workspace/merged_output/task_001_full.mp4看到"合并完成"之后,别急着打开视频,先用 ffprobe 校验一下时长和流信息,确认没有丢段:
ffprobe -v error -show_entries format=duration,size -show_entries stream=codec_name,width,height,r_frame_rate -of default=noprint_wrappers=1 task_001_full.mp4输出里重点看三个值:duration应该约等于所有碎片时长之和,codec_name应该和原始碎片一致(无损模式下通常是 h264),r_frame_rate保持原帧率不变。如果 duration 明显偏短,说明有碎片没被读进去,回去检查命名规则和MIN_FILE_SIZE阈值。
再做一个更直观的校验,用 ffmpeg 抽取合并后视频的首帧和尾帧,确认画面没有黑屏:
ffmpeg -y -i task_001_full.mp4 -vf "select=eq(n\,0)" -vframes 1 first_frame.png ffmpeg -y -sseof -0.1 -i task_001_full.mp4 -vframes 1 last_frame.png打开这两张图,首帧应该是第一段碎片的开头画面,尾帧应该是最后一段的结尾画面。如果尾帧是黑的,说明最后一段有坏片或者 auto_fix 没生效,把AUTO_FIX确认成 True 再跑一次。
我实测下来,12 段 4s 的碎片合并成 48s 长视频,整个过程不到 8 秒,CPU 占用很低,因为无损模式基本不做编码运算,只是流复制加容器封装。这也是 openClaw 相比 moviepy 的优势所在——moviepy 会把每一帧解码到内存再重新编码,长视频必崩。
如果你合并的是带音频的 Seedance 输出,校验时再加一条音频流检查:
ffprobe -v error -select_streams a -show_entries stream=codec_name,channels,sample_rate -of default=noprint_wrappers=1 task_001_full.mp4有音频流输出就说明音轨被正确保留了。如果这里为空,检查碎片本身是否带音轨,以及 lossless 模式下音频编码格式是否一致。
验证通过后,这个脚本就可以固化成你的标准后处理步骤了。
5. 常见报错排查:401、local proxy failed 与 reading choices
脚本跑起来之后,报错基本集中在这几类。我按实际遇到的频率排一下,每条给出原因和修法。
报错一:401 Unauthorized
这个通常不是 openClaw 本身报的,而是你在合并后调用了模型接口做后续处理时出现的。原因就一个:API Key 没带或者带错了。检查你的请求头里Authorization: Bearer <你的Key>是否完整,Key 有没有多余空格。如果你用的是 TaoToken 的 API,Key 在控制台的 API Keys 页面生成,接入文档里有标准的请求示例。重新生成一个 Key 替换掉旧的,基本能解决。
报错二:local proxy failed或connection refused
这个报错说明脚本在尝试访问某个本地服务端口,但那个端口没有服务在监听。常见于你把合并脚本和某个本地推理服务串在一起跑,推理服务没启动或者端口被占用。排查步骤:先用netstat -ano | findstr <端口号>(Windows)或lsof -i:<端口号>(Mac/Linux)确认端口状态,再检查服务配置里的端口是否和脚本里写的一致。注意不要在任何配置里填写来路不明的网络中转地址,本地服务就用127.0.0.1加正确端口。
报错三:Error reading choices或invalid response format
这个出现在你调用模型接口拿返回结果时,返回体不是预期的 JSON 结构。原因可能是请求参数里model字段填的模型 ID 不存在,或者stream参数和解析逻辑不匹配。修法:先用模型对话页面手动发一条测试请求,确认模型 ID 和返回格式,再把同样的参数搬到脚本里。如果你在脚本里用了流式返回,记得按 SSE 格式逐行解析,不要直接json.loads整个响应体。
报错四:ffmpeg not found
openClaw 底层依赖 ffmpeg 可执行文件。如果系统 PATH 里没有 ffmpeg,会报这个。Windows 下去 ffmpeg 官网下载解压,把bin目录加到系统环境变量;Mac 用brew install ffmpeg;Linux 用apt install ffmpeg。装完在终端敲ffmpeg -version能出版本号就行。
报错五:合并后时长对但画面卡顿
这不是报错,是现象。原因通常是碎片帧率不一致,无损模式下 ffmpeg 不会自动统一帧率。修法:把所有碎片先统一转成相同帧率再合并,或者关掉 lossless 让 openClaw 走一次重编码对齐。命令参考:
ffmpeg -i input.mp4 -r 24 -c:v libx264 -crf 18 output.mp4把每个碎片都过一遍这个命令,再跑合并脚本。
排查的时候记住一个原则:先确认单个碎片能正常播放,再确认排序正确,最后才怀疑合并逻辑。大部分问题都出在前两步。
6. 把合并脚本接入你的 AI 视频工作流
脚本能跑通之后,下一步是让它变成工作流的一部分,而不是每次手动执行。有两种接法,看你的使用习惯。
第一种是直接在 Seedance 推理脚本末尾调用合并函数。推理跑完,碎片落在输出目录,紧接着执行merge_seedance_video(),一条命令从生成到成品全自动。这种适合单任务场景,改一下路径就能用。
第二种是批量模式,遍历工作区下所有task_xxx子目录,逐个合并。核心逻辑就是把单任务函数包一层循环:
def batch_merge(root_dir, output_dir): for task in os.listdir(root_dir): task_path = os.path.join(root_dir, task) if not os.path.isdir(task_path): continue out_path = os.path.join(output_dir, f"{task}_full.mp4") segments = get_sorted_segments(task_path) if not segments: print(f"[跳过] {task} 无有效碎片") continue merger = openclaw.VideoMerger(segments, auto_fix=True, lossless=True) merger.merge(output_path=out_path) print(f"[完成] {task} -> {out_path}")批量模式下建议加个日志文件,把每个任务的碎片数量、跳过数量、输出路径记下来,出问题好回溯。
如果你要把这个流程做成长期跑的自动化任务,比如每天定时处理新生成的素材,可以考虑用 TaoToken 的 Coding Plan 来托管脚本调度。它适合这种需要反复执行、带模型调用的编码任务,比自己在本地挂 cron 省心。模型对话页面可以用来快速测试接口返回,API Keys 页面管理你的调用凭证,接入文档里有完整的参数说明。
最后说一个实用技巧:合并完成后,把原始碎片目录打个压缩包归档,别直接删。Seedance 重新生成同一批素材成本不低,万一合并参数需要调整,原始碎片还在就能重跑。归档目录和merged_output分开存放,脚本遍历时加个目录白名单,避免误读。
到这里,从目录规范、脚本编写、参数配置到结果校验和排错,整条链路就闭环了。你可以先拿一批 5 到 10 段的碎片试跑,确认排序和画质没问题,再放到批量任务里。