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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 12:35:23