用python脚本在影片渲染队列里渲染影片的异步问题没有解决

import unreal
import time

CONFIG_PATH = "/Game/DesertOsais"


def get_all_camera_actors_in_range(editor_actor_subs, start, end):
    """获取指定范围的相机"""
    all_actors = editor_actor_subs.get_all_level_actors()
    camera_list = []
    for i in range(start, end + 1):
        target_name = f"相机{i}"
        found_actor = None
        for actor in all_actors:
            if actor.get_actor_label() == target_name:
                found_actor = actor
                break
        if found_actor:
            try:
                video_index = found_actor.get_editor_property("视频序号")
                camera_list.append({
                    'actor': found_actor,
                    'video_index': video_index,
                    'label': target_name
                })
                unreal.log(f"✅ 找到: {target_name} (视频序号: {video_index})")
            except:
                unreal.log_warning(f"⚠️ {target_name} 获取视频序号失败")
    camera_list.sort(key=lambda x: x['video_index'])
    return camera_list


def switch_camera_in_sequencer(level_sequence, camera_actor):
    """切换Sequencer相机剪切"""
    try:
        camera_cut_track = None
        for track in level_sequence.get_tracks():
            if isinstance(track, unreal.MovieSceneCameraCutTrack):
                camera_cut_track = track
                break
        if not camera_cut_track:
            unreal.log_error("❌ 未找到相机剪切轨道")
            return False

        bindings = level_sequence.get_bindings()
        target_binding = None
        for binding in bindings:
            try:
                bound_objects = unreal.SequencerTools.get_bound_objects(
                    level_sequence, [binding], level_sequence.get_time_range()
                )
                if bound_objects:
                    for obj in bound_objects:
                        if obj.bound_object == camera_actor:
                            target_binding = binding
                            break
            except:
                continue
            if target_binding:
                break

        if not target_binding:
            target_binding = level_sequence.add_possessable(camera_actor)

        sections = camera_cut_track.get_sections()
        section = sections[0] if sections else camera_cut_track.add_section()
        section.set_range(level_sequence.get_playback_start(), level_sequence.get_playback_end())

        binding_proxy = unreal.MovieSceneObjectBindingID()
        binding_proxy.set_editor_property("Guid", target_binding.get_id())
        section.set_editor_property("CameraBindingID", binding_proxy)

        unreal.log(f"   ✓ 相机剪切已切换到: {camera_actor.get_actor_label()}")
        return True
    except:
        unreal.log_error("❌ 切换相机失败")
        return False


def load_render_config():
    """加载渲染配置"""
    try:
        config = unreal.load_object(None, CONFIG_PATH)
        if config:
            unreal.log(f"✅ 成功加载渲染配置: {CONFIG_PATH}")
            return config
        else:
            unreal.log_error(f"❌ 配置路径错误!")
            return None
    except Exception as e:
        unreal.log_error(f"❌ 加载配置失败: {str(e)}")
        return None


def render_job(video_index, level_sequence, config):
    try:
        # 获取渲染队列子系统
        queue_sub = unreal.get_editor_subsystem(unreal.MoviePipelineQueueSubsystem)
        queue = queue_sub.get_queue()

        # 获取队列中已有的任务
        jobs = queue.get_jobs()
        if not jobs:
            unreal.log_error("   ❌ 渲染队列为空,请先添加任务")
            return False

        # 使用第一个任务
        job = jobs[0]
        job.job_name = str(video_index)  # 工作名称=视频序号
        unreal.log(f"   ✓ 获取已有任务: {job.job_name}")

        # 更新任务配置
        job.sequence = unreal.SoftObjectPath(level_sequence.get_path_name())
        job.set_configuration(config)

        unreal.log(f"   ⏳ 开始渲染视频序号 {video_index},等待完成...")

        # 使用标准的渲染队列执行方式
        queue_sub.render_queue_with_executor_instance(unreal.MoviePipelinePIEExecutor())

        # 等待渲染完全结束 - 多重检测确保渲染真正完成
        max_wait_time = 300  # 最大等待时间(秒),防止无限等待
        wait_count = 0
        while queue_sub.is_rendering():
            time.sleep(1)
            wait_count += 1
            if wait_count % 10 == 0:  # 每10秒输出一次进度
                unreal.log(f"   ⏸ 渲染进行中... ({wait_count}秒)")
            if wait_count > max_wait_time:
                unreal.log_warning(f"   ⚠️ 渲染超时,强制继续")
                break

        # 额外等待,确保预览窗口完全关闭和资源释放
        unreal.log(f"   ⏸ 等待资源释放...")
        time.sleep(1)

        unreal.log(f"   ✓ 渲染完成: {video_index}")
        return True
    except Exception as e:
        unreal.log_error(f"   ❌ 渲染失败: {str(e)}")
        return False


def batch_render_cameras(start=1, end=3):
    """主批量渲染函数"""
    unreal.log(f"🎬 开始批量渲染任务,范围: {start}-{end}")
    editor_actor_subs = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)

    # 获取当前关卡序列
    try:
        level_sequence = unreal.LevelSequenceEditorBlueprintLibrary.get_current_level_sequence()
    except:
        unreal.log_error("❌ 请先打开关卡序列!")
        return

    if not level_sequence:
        unreal.log_error("❌ 未打开任何关卡序列!")
        return

    unreal.log(f"📋 当前关卡序列: {level_sequence.get_name()}")

    # 加载配置
    render_config = load_render_config()
    if not render_config:
        return

    # 获取相机列表
    camera_list = get_all_camera_actors_in_range(editor_actor_subs, start, end)
    if not camera_list:
        unreal.log_warning("⚠️ 未找到相机")
        return

    unreal.log(f"🎯 共找到 {len(camera_list)} 个相机需要渲染")

    # 批量处理
    for i, cam in enumerate(camera_list, 1):
        actor = cam['actor']
        idx = cam['video_index']
        name = cam['label']

        unreal.log(f"\n🔄 [{i}/{len(camera_list)}] 正在处理: {name} (视频序号: {idx})")

        # 启用相机
        actor.set_editor_property("是否使用?", True)
        unreal.log(f"   ✓ 设置 '是否使用?' = True")

        # 切换相机
        if not switch_camera_in_sequencer(level_sequence, actor):
            actor.set_editor_property("是否使用?", False)
            continue

        time.sleep(0.5)

        # 执行渲染(等待完成)
        render_job(idx, level_sequence, render_config)

        # 禁用相机
        actor.set_editor_property("是否使用?", False)
        unreal.log(f"   ✓ 设置 '是否使用?' = False")
        time.sleep(0.5)

    unreal.log(f"\n🎉 批量渲染任务完成!共处理 {len(camera_list)} 个相机")


# ==================== 运行脚本 ====================
if __name__ == "__main__":
    # 渲染 1-3 号相机,直接运行!
    batch_render_cameras(start=1, end=2)

其中,queue_sub.render_queue_with_executor_instance(unreal. MoviePipelinePIEExecutor())开启渲染后,异步执行,我试了很多方法都没有用,python脚本怎么才能控制渲染当前视频完毕后再切换到下一个相机渲染视频啊
我原本的意图就是做一个镜头的批量渲染,去替代人为手动操作,我的版本是5.6.1

已解决:
import unreal

CONFIG_PATH = “” #影片渲染流程Object路径
TARGET_CAMERA_NAME = “” #相机名称前缀

必须全局持有,防止 Python GC 销毁导致 UE 回调失效

_g_renderer = None

class BatchRenderer:
def init(self):
self.queue_sub = None
self.executor = None
self.camera_list =
self.current_idx = 0
self.level_sequence = None
self.render_config = None
self.editor_actor_subs = None

    # 用于 Slate Tick 延迟
    self._tick_handle = None
    self._pending_frames = 0

# ==================== 辅助:保存与路径 ====================
def _save_all(self):
    """保存当前关卡和序列,消除'未保存映射'弹窗"""
    try:
        unreal.EditorLevelLibrary.save_current_level()
        unreal.log("   💾 当前关卡已保存")
    except Exception as e:
        unreal.log_warning(f"   ⚠️ 保存关卡失败: {e}")

    try:
        if self.level_sequence:
            unreal.EditorAssetLibrary.save_loaded_asset(self.level_sequence)
            unreal.log("   💾 当前序列已保存")
    except Exception as e:
        unreal.log_warning(f"   ⚠️ 保存序列失败: {e}")

def _get_current_map_path(self):
    """获取当前编辑器的地图路径,用于显式设置 Job.map"""
    try:
        world = unreal.EditorLevelLibrary.get_editor_world()
        if world:
            return world.get_path_name()
    except Exception as e:
        unreal.log_warning(f"   ⚠️ 获取地图路径失败: {e}")
    return None

# ==================== 相机与序列 ====================
def get_all_camera_actors_in_range(self, start, end):
    """获取指定范围的相机"""
    all_actors = self.editor_actor_subs.get_all_level_actors()
    camera_list = []
    for i in range(start, end + 1):
        target_name = f"{TARGET_CAMERA_NAME}{i}"
        found_actor = None
        for actor in all_actors:
            if actor.get_actor_label() == target_name:
                found_actor = actor
                break
        if found_actor:
            try:
                video_index = found_actor.get_editor_property("视频序号")
                camera_list.append({
                    'actor': found_actor,
                    'video_index': video_index,
                    'label': target_name
                })
                unreal.log(f"✅ 找到: {target_name} (视频序号: {video_index})")
            except:
                unreal.log_warning(f"⚠️ {target_name} 获取视频序号失败")
    camera_list.sort(key=lambda x: x['video_index'])
    return camera_list

def switch_camera_in_sequencer(self, camera_actor):
    """切换 Sequencer 相机剪切"""
    try:
        camera_cut_track = None
        for track in self.level_sequence.get_tracks():
            if isinstance(track, unreal.MovieSceneCameraCutTrack):
                camera_cut_track = track
                break
        if not camera_cut_track:
            unreal.log_error("❌ 未找到相机剪切轨道")
            return False

        bindings = self.level_sequence.get_bindings()
        target_binding = None
        for binding in bindings:
            try:
                bound_objects = unreal.SequencerTools.get_bound_objects(
                    self.level_sequence, [binding], self.level_sequence.get_time_range()
                )
                if bound_objects:
                    for obj in bound_objects:
                        if obj.bound_object == camera_actor:
                            target_binding = binding
                            break
            except:
                continue
            if target_binding:
                break

        if not target_binding:
            target_binding = self.level_sequence.add_possessable(camera_actor)

        sections = camera_cut_track.get_sections()
        section = sections[0] if sections else camera_cut_track.add_section()
        section.set_range(self.level_sequence.get_playback_start(), self.level_sequence.get_playback_end())

        binding_proxy = unreal.MovieSceneObjectBindingID()
        binding_proxy.set_editor_property("Guid", target_binding.get_id())
        section.set_editor_property("CameraBindingID", binding_proxy)

        unreal.log(f"   ✓ 相机剪切已切换到: {camera_actor.get_actor_label()}")
        return True
    except:
        unreal.log_error("❌ 切换相机失败")
        return False

def load_render_config(self):
    """加载渲染配置"""
    try:
        config = unreal.load_object(None, CONFIG_PATH)
        if config:
            unreal.log(f"✅ 成功加载渲染配置: {CONFIG_PATH}")
            return config
        else:
            unreal.log_error(f"❌ 配置路径错误!")
            return None
    except Exception as e:
        unreal.log_error(f"❌ 加载配置失败: {str(e)}")
        return None

# ==================== 核心:异步回调与延迟 ====================
def _on_render_finished(self, executor, success):
    """UE 渲染完成后自动调用(预览窗口已关闭,文件已保存)"""
    unreal.log(f"   ✓ 渲染完成回调触发,success={success}")

    # 禁用当前相机
    if self.current_idx < len(self.camera_list):
        current_actor = self.camera_list[self.current_idx]['actor']
        current_actor.set_editor_property("是否使用?", False)
        unreal.log(f"   ✓ 设置 '{self.camera_list[self.current_idx]['label']}' 是否使用? = False")

    self.current_idx += 1

    if self.current_idx < len(self.camera_list):
        # 关键修复:延迟 120 帧(约 2 秒)再启动下一轮,
        # 确保上一个 PIE 窗口在 C++ 层完全销毁,避免 Error 87 崩溃
        self._schedule_next(delay_frames=30)
    else:
        unreal.log(f"\n🎉 批量渲染任务完成!共处理 {len(self.camera_list)} 个相机")
        self._cleanup()

def _schedule_next(self, delay_frames):
    """通过 Slate Post Tick 做非阻塞延迟"""
    self._pending_frames = delay_frames
    self._tick_handle = unreal.register_slate_post_tick_callback(self._on_wait_tick)

def _on_wait_tick(self, delta_time):
    """Slate Post Tick 回调"""
    self._pending_frames -= 1
    if self._pending_frames <= 0:
        # 注销 Tick 回调,避免空转
        if self._tick_handle:
            try:
                unreal.unregister_slate_post_tick_callback(self._tick_handle)
            except:
                pass
            self._tick_handle = None
        self._process_next_camera()

def _cleanup(self):
    """清理资源"""
    global _g_renderer
    if self._tick_handle:
        try:
            unreal.unregister_slate_post_tick_callback(self._tick_handle)
        except:
            pass
    self.executor = None
    self.queue_sub = None
    _g_renderer = None

def _process_next_camera(self):
    """处理下一个相机"""
    if self.current_idx >= len(self.camera_list):
        unreal.log(f"\n🎉 批量渲染任务完成!共处理 {len(self.camera_list)} 个相机")
        self._cleanup()
        return

    cam = self.camera_list[self.current_idx]
    actor = cam['actor']
    idx = cam['video_index']
    name = cam['label']

    unreal.log(f"\n🔄 [{self.current_idx + 1}/{len(self.camera_list)}] 正在处理: {name} (视频序号: {idx})")

    # 启用相机
    actor.set_editor_property("是否使用?", True)
    unreal.log(f"   ✓ 设置 '是否使用?' = True")

    # 切换相机
    if not self.switch_camera_in_sequencer(actor):
        actor.set_editor_property("是否使用?", False)
        self.current_idx += 1
        self._schedule_next(delay_frames=60)  # 失败也缓冲一下
        return

    # 启动渲染
    self._start_render(idx)

def _start_render(self, video_index):
    """启动渲染"""
    try:
        # ===== 关键修复 1:渲染前强制保存,消除弹窗 =====
        # self._save_all()

        self.queue_sub = unreal.get_editor_subsystem(unreal.MoviePipelineQueueSubsystem)
        queue = self.queue_sub.get_queue()

        # 清空旧 Job,避免配置残留
        queue.delete_all_jobs()

        # 分配全新 Job
        job = queue.allocate_new_job(unreal.MoviePipelineExecutorJob)
        job.job_name = str(video_index)
        job.sequence = unreal.SoftObjectPath(self.level_sequence.get_path_name())

        # ===== 关键修复 2:显式设置 map,否则 UE 认为未指定关卡 =====
        map_path = self._get_current_map_path()
        if map_path:
            job.set_editor_property("map", unreal.SoftObjectPath(map_path))
            unreal.log(f"   🗺 目标地图: {map_path}")

        job.set_configuration(self.render_config)

        unreal.log(f"   ⏳ 开始渲染视频序号 {video_index}...")

        # 创建 Executor 并绑定完成回调
        self.executor = unreal.MoviePipelinePIEExecutor(self.queue_sub)
        self.executor.on_executor_finished_delegate.add_callable_unique(self._on_render_finished)

        # 异步启动(非阻塞,不会卡死编辑器)
        self.queue_sub.render_queue_with_executor_instance(self.executor)

    except Exception as e:
        unreal.log_error(f"   ❌ 渲染启动失败: {str(e)}")
        self.current_idx += 1
        self._schedule_next(delay_frames=90)

def start(self, start=1, end=3):
    """开始批量渲染"""
    unreal.log(f"🎬 开始批量渲染任务,范围: {start}-{end}")
    self.editor_actor_subs = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)

    try:
        self.level_sequence = unreal.LevelSequenceEditorBlueprintLibrary.get_current_level_sequence()
    except:
        unreal.log_error("❌ 请先打开关卡序列!")
        return

    if not self.level_sequence:
        unreal.log_error("❌ 未打开任何关卡序列!")
        return

    unreal.log(f"📋 当前关卡序列: {self.level_sequence.get_name()}")

    # 加载配置
    self.render_config = self.load_render_config()
    if not self.render_config:
        return

    # 获取相机
    self.camera_list = self.get_all_camera_actors_in_range(start, end)
    if not self.camera_list:
        unreal.log_warning("⚠️ 未找到相机")
        return

    unreal.log(f"🎯 共找到 {len(self.camera_list)} 个相机需要渲染")

    self.current_idx = 0
    self._process_next_camera()

def batch_render_cameras(start=1, end=3):
“”“批量渲染相机”“”
global _g_renderer
_g_renderer = BatchRenderer()
_g_renderer.start(start, end)

if name == “main”:
batch_render_cameras(start=1, end=10)