MoviePy VideoClip进程未终止致文件删除失败问题排查
解决MoviePy处理后删除文件触发WinError32(文件被占用)问题
问题场景
编写Python脚本实现以下功能:从CSV读取视频信息,通过yt_dlp下载后用MoviePy剪辑,同时删除存储目录中不在CSV内的冗余视频。执行删除循环时触发WinError 32错误,提示文件被其他进程占用。已尝试调用clip.close()释放VideoClip、注释下载循环,问题仍未解决。
相关代码
主脚本代码
if __name__ == '__main__': import csv from moviepy.tools import cvsecs from yt_dlp import YoutubeDL import os import moviepy.editor as mpe from src.config.dev_config import download_video_location, temp_folder_path, csv_path from src.services.videos.double_video import process_split_video from src.services.videos.video import write_video_to_file from pytube import extract # Read CSV file into a set of tuples csv_set = set() with open(csv_path, 'r', newline='', encoding='utf-8') as csv_file: csv_reader = csv.reader(csv_file, delimiter=';') header = next(csv_reader) for row in csv_reader: url, start_time = row url_id = extract.video_id(url) start_time = int(cvsecs(start_time)) name = f"{str(url_id)}_{str(start_time)}" csv_set.add(name) # Read video files in the storage folder into a set of tuples storage_set = set() for filename in os.listdir(download_video_location): name = filename.split(".")[0] storage_set.add(name) # Calculate the difference between sets videos_to_download = csv_set - storage_set videos_to_delete = storage_set - csv_set # Download videos from videos_to_download for video in videos_to_download: video_start = int(video.split("_")[-1]) video_id = "_".join(video.rsplit("_", 1)[:-1]) video_url = f"https://www.youtube.com/watch?v={video_id}" params = { "outtmpl": os.path.join(temp_folder_path, "%(id)s.%(ext)s"), "format_sort": ["res:720"], "format": "bv", "noplaylist": True, } with YoutubeDL(params) as ydl: info = ydl.extract_info(video_url, download=True) path = ydl.prepare_filename(info) clip = mpe.VideoFileClip(path).without_audio() clip = process_split_video(clip) subclip = clip.subclip(video_start, video_start + 60) write_video_to_file(subclip, f"{video_id}_{video_start}") clip.close() clip = None # Delete videos from videos_to_delete for video in videos_to_delete: file_to_delete = [file for file in os.listdir(download_video_location) if file.startswith(video)] if file_to_delete: file_to_delete = file_to_delete[0] filepath_to_delete = os.path.join(download_video_location, file_to_delete) try: os.remove(filepath_to_delete) print(f"File {file_to_delete} deleted successfully.") break except Exception as e: print(f"Error deleting file {file_to_delete}: {str(e)}")
调用的辅助函数
def process_split_video(clip, strict_size: bool = True): # crop to (9, 8) because it's half of (9, 16) result = video.crop_to_ratio(clip, (9, 8)) result = video.resize_height(result, 1920 / 2, strict_size=strict_size) return result
def write_video_to_file(clip, name_without_extension): output_path = dev_config.video_output_path os.makedirs(output_path, exist_ok=True) clip.write_videofile(f"{output_path}/{name_without_extension}.mp4")
报错信息
Error deleting file n_Dv4JMiwK8_178.mp4: [WinError 32] Процесс не может получить доступ к файлу, так как этот файл занят другим процессом: 'C:\Users\nikita_turda\PycharmProjects\job\storage\n_Dv4JMiwK8_178.mp4'
解决方案
1. 显式关闭所有VideoClip实例
当前仅关闭了原clip对象,但subclip和process_split_video返回的新clip也可能持有文件句柄,需全部关闭:
# 修改下载循环中的资源释放部分 subclip = clip.subclip(video_start, video_start + 60) write_video_to_file(subclip, f"{video_id}_{video_start}") # 先关闭subclip subclip.close() subclip = None # 关闭处理后的clip clip.close() clip = None
2. 优化write_videofile调用,避免进程残留
MoviePy的write_videofile依赖ffmpeg子进程,若未正确终止会占用文件。修改write_video_to_file函数,添加参数并显式关闭clip:
def write_video_to_file(clip, name_without_extension): output_path = dev_config.video_output_path os.makedirs(output_path, exist_ok=True) # 禁用日志输出,减少子进程残留 clip.write_videofile( f"{output_path}/{name_without_extension}.mp4", verbose=False, logger=None ) # 写完后立即关闭clip clip.close() clip = None
3. 强制清理所有未释放的MoviePy资源
在删除文件前,调用MoviePy内置的清理方法,强制释放所有未关闭的clip:
# Delete videos from videos_to_delete import moviepy.editor as mpe # 强制清理所有clip资源 mpe.close_all_clips() for video in videos_to_delete: file_to_delete = [file for file in os.listdir(download_video_location) if file.startswith(video)] if file_to_delete: file_to_delete = file_to_delete[0] filepath_to_delete = os.path.join(download_video_location, file_to_delete) try: os.remove(filepath_to_delete) print(f"File {file_to_delete} deleted successfully.") except Exception as e: print(f"Error deleting file {file_to_delete}: {str(e)}")
4. 增加延迟,给系统足够时间释放资源
Windows文件句柄释放可能存在延迟,在删除前添加短暂等待:
import time # 删除前等待2秒,确保资源释放 time.sleep(2) for video in videos_to_delete: # ... 原删除代码
总结
WinError32本质是文件句柄未被正确释放,通过显式关闭所有VideoClip实例、优化ffmpeg调用、强制清理资源这几个步骤,基本可以解决文件被占用的问题。
内容的提问来源于stack exchange,提问作者Nikita Turda
相关产品推荐
相关产品推荐

