media_composer
视频合并、配音与导出。
视频合成与导出:按顺序拼接视频、为视频替换/添加音轨、转码导出 MP4。
边界:基于 AVFoundation,操作可能耗时较长(阻塞脚本数秒到两分钟)。输入/输出路径须为 App 可访问的本地文件;合成放在按钮回调,不要在 body() 中自动执行。素材可先由 video_recorder 或 photos 获得。
#模块概览
| 项 | 说明 |
|---|---|
| 导入 | import media_composer |
| 适合做什么 | 多段录像拼接、替换背景音、统一导出 MP4 |
| 调用时机 | 用户确认后调用;显示进度/状态中 |
| 推荐顺序 | 确认源文件存在 → merge_videos / merge_audio / export → 校验输出路径 |
| 输出格式 | 当前 Bridge 导出为 .mp4 |
#快速开始
下面脚本检查素材是否存在,再合并导出:
已复制
import os
import media_composer
clip = os.path.join(os.path.expanduser("~/Documents"), "clip1.mov")
out = os.path.join(os.path.expanduser("~/Documents"), "merged.mp4")
if not os.path.exists(clip):
print("请先把视频放到:", clip)
else:
try:
path = media_composer.merge_videos([clip], out)
print("已导出:", path)
except media_composer.MediaComposerError as exc:
print("合成失败:", exc, f"code={exc.code}")
为视频替换音轨:
已复制
import media_composer
media_composer.merge_audio("video.mov", "audio.m4a", "output.mp4")
#AppUI 示例
合成放在按钮回调;先检查文件是否存在再执行。
已复制
import appui
import os
import media_composer
DOCS = os.path.expanduser("~/Documents")
CLIP = os.path.join(DOCS, "clip1.mov")
OUTPUT = os.path.join(DOCS, "merged.mp4")
state = appui.State(
input_path=CLIP,
output_path=OUTPUT,
result="—",
status="准备好素材后点击合成",
)
def run_merge():
if not os.path.exists(state.input_path):
state.batch_update(
result="—",
status=f"找不到源文件: {state.input_path}",
)
return
try:
path = media_composer.merge_videos([state.input_path], state.output_path)
state.batch_update(
result=path or state.output_path,
status="合成完成",
)
except media_composer.MediaComposerError as exc:
state.batch_update(
result="—",
status=f"失败: {exc}",
)
def body():
exists = "存在" if os.path.exists(state.input_path) else "缺失"
return appui.NavigationStack(
appui.Form([
appui.Section("素材", [
appui.LabeledContent("源视频", value=state.input_path),
appui.LabeledContent("状态", value=exists),
appui.LabeledContent("输出", value=state.output_path),
]),
appui.Section("操作", [
appui.Button("合并导出 MP4", action=run_merge)
.button_style("bordered_prominent"),
appui.LabeledContent("结果", value=state.result),
appui.Text(state.status).foreground_color("secondaryLabel"),
], footer="请先把 clip1.mov 放到 Documents;合成可能耗时。"),
]).navigation_title("视频合成")
)
appui.run(body, state=state)
#API 参考
#速查
| API | 作用 |
|---|---|
merge_videos(paths, output_path) | 按顺序拼接视频 |
merge_audio(video_path, audio_path, output_path) | 视频 + 音轨合成 |
export(input_path, output_path, format) | 转码/导出 |
MediaComposerError | 操作失败时抛出 |
#merge_videos
merge_videos(paths, output_path) -> str
已复制
path = media_composer.merge_videos(
["part1.mov", "part2.mov"],
"final.mp4",
)
按列表顺序拼接各文件的视频轨道;无视频轨的文件会被跳过。返回输出路径。
#merge_audio
merge_audio(video_path, audio_path, output_path) -> str
已复制
path = media_composer.merge_audio(
"silent.mov",
"voice.m4a",
"with_audio.mp4",
)
取视频的画面轨与音频文件的音轨,时长取两者较短者。
#export
export(input_path, output_path, format="mp4") -> str
已复制
path = media_composer.export("input.mov", "output.mp4")
format 为兼容参数;当前 Bridge 统一导出 MP4。
#异常
code | 含义 |
|---|---|
invalid_input | 路径缺失或列表为空 |
compose_failed | 无法创建合成轨道 |
export_failed | 导出会话失败或超时 |
#常见错误
| 错误写法 | 后果 | 修正 |
|---|---|---|
在 body() 里合成 | 每次刷新都重跑 | 放进按钮回调 |
| 源文件不存在 | export_failed / 空输出 | 先 os.path.exists() |
空 paths 列表 | invalid_input | 至少提供一个视频 |
| 合成中反复点按钮 | 重复阻塞 | 加状态锁或禁用按钮 |
#相关文档
| 文档 | 用途 |
|---|---|
| video_recorder | 采集视频素材 |
| avplayer | 预览合成结果 |
| photos | 保存视频到相册 |
| audio_recorder | 采集配音素材 |
#预期效果
运行示例后,界面应出现文档描述的目标结果;若与预期不符,先看「失败路径」并按返回值或日志排查。