You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Discord机器人中实现MoviePy视频导出进度条?

解决MoviePy异步回调无法更新Discord机器人进度的问题

问题场景

我在开发Discord机器人时,想用Python的MoviePy库导出视频并实时展示进度,但把Logger的回调函数写成async def后,发现这个函数根本没被正常调用,没法更新Discord消息,求解决办法。原代码如下:

message = None

async def progress_bar(bar, percentage):
    print(percentage)
    global message
    await message.edit(content=f'Loading... {bar} ({percentage}/100)')

class MyBarLogger(ProgressBarLogger):
    def __init__(self):
        super().__init__()
        self.percentage = 0  # Initialize percentage

    async def bars_callback(self, bar, attr, value, old_value=None):
        # Every time the logger progress is updated, this function is called
        self.percentage = (value / self.bars[bar]['total']) * 100

        def generate_loading_bar(progress, length=20):
            bar_length = int(length * progress)
            return '[' + '=' * bar_length + '-' * (length - bar_length) + ']'

        total_iterations = 100  # Adjust the total number of iterations as needed
        progress = self.percentage / total_iterations

        bar = generate_loading_bar(progress)

        await progress_bar(bar, self.percentage)

async def run_loading_bar(final_clip, output_path):
    logger = MyBarLogger()

    with ThreadPoolExecutor() as executor:
        loop = asyncio.get_event_loop()
        await loop.run_in_executor(executor, lambda: final_clip.write_videofile(output_path, threads=8, codec='libx264', audio_codec='aac', logger=logger))

    final_clip.close()

@bot.tree.command(name="generate",
                  description="",
                  guild=discord.Object(id=GUILD_ID))
async def generate_motivational_video_command(interaction: discord.Interaction):
    global message

    message = await interaction.response.send_message('Loading...')
    final_clip = VideoFileClip(r'video_path.mp4')
    await run_loading_bar(final_clip, 'test.mp4')

解决方案

问题核心是MoviePy的ProgressBarLogger是同步实现的,它不会自动处理异步的bars_callback方法——你用async def定义的回调根本不会被await,自然不会执行任何逻辑。

要解决这个问题,需要把同步的MoviePy回调和异步的Discord操作桥接起来,具体修改如下:

修改后的完整代码

import asyncio
from concurrent.futures import ThreadPoolExecutor
from moviepy.video.io.VideoFileClip import VideoFileClip
from moviepy.video.io.progress_bar import ProgressBarLogger
import discord

# 异步更新Discord消息的函数
async def progress_bar(message, bar, percentage):
    print(f"当前进度: {percentage:.1f}%")
    await message.edit(content=f'Loading... {bar} ({percentage:.1f}/100)')

class MyBarLogger(ProgressBarLogger):
    def __init__(self, message, loop):
        super().__init__()
        self.percentage = 0
        self.message = message  # 保存Discord消息对象,不用全局变量
        self.loop = loop        # 保存Discord的事件循环

    def bars_callback(self, bar, attr, value, old_value=None):
        # 同步回调方法,内部调用异步逻辑
        self.percentage = (value / self.bars[bar]['total']) * 100

        # 生成进度条
        def generate_loading_bar(progress, length=20):
            bar_length = int(length * progress)
            return '[' + '=' * bar_length + '-' * (length - bar_length) + ']'

        # 修正进度计算:直接用百分比除以100
        progress = self.percentage / 100
        bar = generate_loading_bar(progress)

        # 把异步任务提交到Discord的事件循环中执行
        asyncio.run_coroutine_threadsafe(
            progress_bar(self.message, bar, self.percentage),
            self.loop
        )

async def run_loading_bar(final_clip, output_path, message, loop):
    logger = MyBarLogger(message, loop)

    with ThreadPoolExecutor() as executor:
        await loop.run_in_executor(
            executor, 
            lambda: final_clip.write_videofile(
                output_path, 
                threads=8, 
                codec='libx264', 
                audio_codec='aac', 
                logger=logger
            )
        )

    final_clip.close()

@bot.tree.command(name="generate",
                  description="生成视频并展示进度",
                  guild=discord.Object(id=GUILD_ID))
async def generate_motivational_video_command(interaction: discord.Interaction):
    # 发送初始加载消息
    message = await interaction.response.send_message('Loading...')
    # 获取当前Discord的事件循环
    loop = asyncio.get_event_loop()
    
    final_clip = VideoFileClip(r'video_path.mp4')
    await run_loading_bar(final_clip, 'test.mp4', message, loop)

关键修改说明

  1. 同步回调桥接异步操作:把bars_callback改回同步方法,用asyncio.run_coroutine_threadsafe将异步的progress_bar提交到Discord的事件循环中执行——因为MoviePy的导出是在子线程中运行的,跨线程调用异步函数必须用这个方法。
  2. 移除全局变量:将Discord的message对象和事件循环loop直接传递给MyBarLogger,避免全局变量带来的并发问题。
  3. 修正进度计算:删除多余的total_iterations变量,直接用self.percentage / 100计算进度,逻辑更清晰。
  4. 优化进度显示:给百分比添加小数点,显示更精准。

内容的提问来源于stack exchange,提问作者Omar Taoufik

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 04:57:32