如何在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)
关键修改说明
- 同步回调桥接异步操作:把
bars_callback改回同步方法,用asyncio.run_coroutine_threadsafe将异步的progress_bar提交到Discord的事件循环中执行——因为MoviePy的导出是在子线程中运行的,跨线程调用异步函数必须用这个方法。 - 移除全局变量:将Discord的
message对象和事件循环loop直接传递给MyBarLogger,避免全局变量带来的并发问题。 - 修正进度计算:删除多余的
total_iterations变量,直接用self.percentage / 100计算进度,逻辑更清晰。 - 优化进度显示:给百分比添加小数点,显示更精准。
内容的提问来源于stack exchange,提问作者Omar Taoufik
相关产品推荐
相关产品推荐

