python调用ffmpeg.exe封装装饰类调用

复制代码
import os
import sys
import time
import ctypes # 系统交互
import threading #进程
import subprocess #调用
from functools import wraps #装饰
import signal  # 用于处理信号

# 基础参数
FFMPEG_BIN_DIR = r"../bin"  # FFmpeg所在目录
save_path = "video" # 保存文件地址


# ************************** 1. 强制以管理员身份运行(Windows)**************************
def is_admin():
    """判断当前进程是否有管理员权限"""
    try:
        return ctypes.windll.shell32.IsUserAnAdmin()
    except:
        return False

def run_as_admin():
    """重新以管理员身份启动当前脚本"""
    if not is_admin():
        # 重新启动脚本并请求管理员权限
        ctypes.windll.shell32.ShellExecuteW(
            None, "runas", sys.executable, " ".join(sys.argv), None, 1
        )
        sys.exit(0)

# 强制管理员运行(移到最前面,确保后续操作有权限)
run_as_admin()

# ************************** 2. 手动指定FFmpeg的路径 **************************
ffmpeg_exe = os.path.join(FFMPEG_BIN_DIR, "ffmpeg.exe")  # FFmpeg可执行文件路径
# 将FFmpeg路径加入环境变量(可选,确保能直接调用)
os.environ["PATH"] = FFMPEG_BIN_DIR + os.pathsep + os.environ["PATH"]

# 验证FFmpeg路径
if not os.path.exists(ffmpeg_exe):
    print(f"错误:未找到ffmpeg.exe,请检查路径:{ffmpeg_exe}")
    sys.exit(1)

# 全局变量
recording_process = None  # 全局存储FFmpeg进程
recording_thread = None  # 录制线程
stop_recording = False  # 停止录制标志
business_thread = None  # 主线程业务逻辑线程

def fix_mp4_duration(input_file, output_file):
    """修复MP4文件的时长信息:通过FFmpeg命令行重新封装为标准MP4"""
    try:
        print(f"\n正在修复视频时长信息:{input_file} -> {output_file}")
        # 构造FFmpeg修复命令行参数
        ffmpeg_cmd = [
            ffmpeg_exe,
            "-i", input_file,  # 输入文件
            "-c", "copy",  # 直接复制流,不重新编码(保留VBR的比特率特征)
            "-movflags", "faststart",  # 将moov原子移到文件开头
            "-y",  # 覆盖输出文件
            output_file  # 输出文件
        ]
        # 执行修复命令,静默运行
        subprocess.run(
            ffmpeg_cmd,
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL,
            check=True
        )
        # 删除原碎片化文件,保留修复后的文件
        os.remove(input_file)
        print(f"时长修复完成,最终文件:{output_file}")
    except subprocess.CalledProcessError as e:
        print(f"\n修复时长失败:FFmpeg执行返回错误码 {e.returncode}")
    except Exception as e:
        print(f"\n修复时长时发生错误:{str(e)}")

def record_screen():
    """录制屏幕的主函数(保留VBR+解决时长偏差)"""
    global recording_process, stop_recording,save_path

    # 统一路径处理
    temp_file = save_path + "_temp.mp4"
    output_file = save_path + ".mp4"
    framerate = 15
    # ---------------------- VBR核心参数(完全保留,可根据需求调整)----------------------
    video_bitrate = "1000k"    # VBR目标比特率
    video_maxrate = "1500k"    # VBR最大比特率(上限)
    video_bufsize = "5000k"    # VBR缓冲区大小(决定比特率波动范围)
    # ---------------------------------------------------------------------------------

    try:
        # 构造FFmpeg录屏命令行参数(核心:保留VBR + 强制恒定帧率 + 移除空音频源)
        ffmpeg_cmd = [
            ffmpeg_exe,
            # 视频源参数(gdigrab抓取桌面)
            "-f", "gdigrab",  # 格式:Windows桌面抓取
            "-framerate", str(framerate),  # 输入帧率(抓取帧率)
            "-thread_queue_size", "1024",  # 队列大小
            "-probesize", "10M",  # 探测缓冲区
            "-draw_mouse", "1",  # 显示鼠标
            "-i", "desktop",  # 输入:整个桌面
            # ---------------------- VBR相关编码参数(核心保留)----------------------
            "-c:v", "libx264",  # x264编码器完美支持VBR
            "-preset", "fast",  # 编码速度/质量预设(不影响VBR)
            "-b:v", video_bitrate,  # VBR目标比特率
            "-maxrate", video_maxrate,  # VBR最大比特率上限
            "-bufsize", video_bufsize,  # VBR缓冲区(关键)
            # ----------------------------------------------------------------------
            # 关键:强制恒定帧率+同步策略(解决时长偏差的核心,与VBR兼容)
            "-r", str(framerate),  # 输出帧率强制等于输入帧率
            "-vsync", "cfr",  # 恒定帧率模式:保证时间轴与实际时长一致
            # 输出格式参数(碎片化,确保中断后可播放)
            "-movflags", "+frag_keyframe+empty_moov",  # 碎片化参数
            "-max_muxing_queue_size", "1024",  # 混流队列大小
            "-pix_fmt", "yuv420p",  # 颜色空间(兼容x264)
            "-y",  # 覆盖已存在的文件
            temp_file  # 临时输出文件
        ]

        # 打印FFmpeg命令行(调试用,可查看VBR参数是否生效)
        print(f"\nFFmpeg执行命令:{' '.join(ffmpeg_cmd)}")

        # 启动FFmpeg录屏进程
        recording_process = subprocess.Popen(
            ffmpeg_cmd,
            stdout=subprocess.DEVNULL,  # 丢弃标准输出
            stderr=subprocess.DEVNULL,  # 丢弃标准错误
            stdin=subprocess.PIPE,
            creationflags=subprocess.CREATE_NEW_PROCESS_GROUP  # Windows下创建新进程组
        )

        print("开始录制屏幕...")

        # 等待录制进程结束或收到停止信号(优化:减少sleep时间,提高响应速度)
        while recording_process.poll() is None and not stop_recording:
            time.sleep(0.2)  # 从0.5秒改为0.2秒,减少检查延迟

        # 优雅停止FFmpeg进程(优化:先尝试发送q,再用信号,减少超时)
        if stop_recording and recording_process.poll() is None:
            print("\n正在停止录制并保存视频...")
            try:
                # 第一步:尝试发送q信号(FFmpeg的标准停止指令),非阻塞
                recording_process.stdin.write(b'q\n')
                recording_process.stdin.flush()
                time.sleep(0.5)  # 给FFmpeg一点时间响应
                # 第二步:等待进程退出,缩短超时时间到5秒
                recording_process.wait(timeout=5)
            except (subprocess.TimeoutExpired, BrokenPipeError):
                # 超时或管道错误,再用信号强制停止
                try:
                    if os.name == 'nt':
                        ctypes.windll.kernel32.GenerateConsoleCtrlEvent(0, recording_process.pid)
                    else:
                        recording_process.send_signal(signal.SIGINT)
                    recording_process.wait(timeout=2)
                except:
                    print("FFmpeg未及时响应,强制终止...")
                    recording_process.terminate()
                    recording_process.wait()
            except Exception as e:
                print(f"停止FFmpeg时出错:{e}")
                recording_process.terminate()
                recording_process.wait()

    except Exception as e:
        print(f"\n录制过程中发生错误:{str(e)}")
    finally:
        # 等待一小段时间确保FFmpeg完全退出(缩短sleep时间)
        time.sleep(0.5)
        # 修复时长(如果临时文件存在,修复时保留VBR特征)
        if os.path.exists(temp_file):
            fix_mp4_duration(temp_file, output_file)
        else:
            print("未找到临时文件,录制可能未开始或已删除")

def stop_recording_thread():
    """停止录制线程"""
    global stop_recording, recording_thread
    stop_recording = True

    if recording_thread and recording_thread.is_alive():
        print("等待录制线程结束...")
        recording_thread.join(timeout=10)  # 从15秒改回10秒(优化后无需更长时间)
        if recording_thread.is_alive():
            print("警告:录制线程未在10秒内结束,强制结束")
        else:
            print("录制已完全停止")
    recording_thread = None

def video_run():
    """屏幕录制装饰器"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            global recording_thread, business_thread
            global stop_recording
            stop_recording = False

            # 启动录制线程
            recording_thread = threading.Thread(target=record_screen,daemon=False)
            recording_thread.start()
            time.sleep(0.5)  # 从1秒改为0.5秒,减少初始化延迟

            # 定义业务逻辑执行函数
            def run_business():
                try:
                    func(*args, **kwargs)
                except Exception as e:
                    print(f"\n主线程业务逻辑发生异常:{str(e)}")

            # 启动业务逻辑线程
            business_thread = threading.Thread(target=run_business, daemon=False)
            business_thread.start()

            # 等待业务逻辑结束
            business_thread.join()
            print("\n主线程业务逻辑执行结束,准备停止录制")
            stop_recording_thread()

        return wrapper
    return decorator

# ************************** 主线程业务逻辑 **************************
@video_run()  # 路径使用原始字符串
def main_business():
    """示例业务逻辑"""
    print("主线程业务逻辑开始执行...")
    time.sleep(10)  # 模拟业务执行10秒
    raise RuntimeError("业务逻辑出错了!")  # 测试异常情况
    print("主线程业务逻辑执行完成!")

# ************************** 程序入口 **************************
if __name__ == "__main__":
    main_business()
    print("主线程运行完毕,程序即将退出")
python 复制代码
import os
import sys
import time
import ctypes # 系统交互
import threading #进程
import subprocess #调用
from functools import wraps #装饰
import signal  # 用于处理信号

# 基础参数
FFMPEG_BIN_DIR = r"../bin"  # FFmpeg所在目录
save_path = "video" # 保存文件地址


# ************************** 1. 强制以管理员身份运行(Windows)**************************
def is_admin():
    """判断当前进程是否有管理员权限"""
    try:
        return ctypes.windll.shell32.IsUserAnAdmin()
    except:
        return False

def run_as_admin():
    """重新以管理员身份启动当前脚本"""
    if not is_admin():
        # 重新启动脚本并请求管理员权限
        ctypes.windll.shell32.ShellExecuteW(
            None, "runas", sys.executable, " ".join(sys.argv), None, 1
        )
        sys.exit(0)

# 强制管理员运行(移到最前面,确保后续操作有权限)
run_as_admin()

# ************************** 2. 手动指定FFmpeg的路径 **************************
ffmpeg_exe = os.path.join(FFMPEG_BIN_DIR, "ffmpeg.exe")  # FFmpeg可执行文件路径
# 将FFmpeg路径加入环境变量(可选,确保能直接调用)
os.environ["PATH"] = FFMPEG_BIN_DIR + os.pathsep + os.environ["PATH"]

# 验证FFmpeg路径
if not os.path.exists(ffmpeg_exe):
    print(f"错误:未找到ffmpeg.exe,请检查路径:{ffmpeg_exe}")
    sys.exit(1)

# 全局变量
recording_process = None  # 全局存储FFmpeg进程
recording_thread = None  # 录制线程
stop_recording = False  # 停止录制标志
business_thread = None  # 主线程业务逻辑线程

def fix_mp4_duration(input_file, output_file):
    """修复MP4文件的时长信息:通过FFmpeg命令行重新封装为标准MP4"""
    try:
        print(f"\n正在修复视频时长信息:{input_file} -> {output_file}")
        # 构造FFmpeg修复命令行参数
        ffmpeg_cmd = [
            ffmpeg_exe,
            "-i", input_file,  # 输入文件
            "-c", "copy",  # 直接复制流,不重新编码(保留VBR的比特率特征)
            "-movflags", "faststart",  # 将moov原子移到文件开头
            "-y",  # 覆盖输出文件
            output_file  # 输出文件
        ]
        # 执行修复命令,静默运行
        subprocess.run(
            ffmpeg_cmd,
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL,
            check=True
        )
        # 删除原碎片化文件,保留修复后的文件
        os.remove(input_file)
        print(f"时长修复完成,最终文件:{output_file}")
    except subprocess.CalledProcessError as e:
        print(f"\n修复时长失败:FFmpeg执行返回错误码 {e.returncode}")
    except Exception as e:
        print(f"\n修复时长时发生错误:{str(e)}")

def record_screen():
    """录制屏幕的主函数(保留VBR+解决时长偏差)"""
    global recording_process, stop_recording,save_path

    # 统一路径处理
    temp_file = save_path + "_temp.mp4"
    output_file = save_path + ".mp4"
    framerate = 15
    # ---------------------- VBR核心参数(完全保留,可根据需求调整)----------------------
    video_bitrate = "1000k"    # VBR目标比特率
    video_maxrate = "1500k"    # VBR最大比特率(上限)
    video_bufsize = "5000k"    # VBR缓冲区大小(决定比特率波动范围)
    # ---------------------------------------------------------------------------------

    try:
        # 构造FFmpeg录屏命令行参数(核心:保留VBR + 强制恒定帧率 + 移除空音频源)
        ffmpeg_cmd = [
            ffmpeg_exe,
            # 视频源参数(gdigrab抓取桌面)
            "-f", "gdigrab",  # 格式:Windows桌面抓取
            "-framerate", str(framerate),  # 输入帧率(抓取帧率)
            "-thread_queue_size", "1024",  # 队列大小
            "-probesize", "10M",  # 探测缓冲区
            "-draw_mouse", "1",  # 显示鼠标
            "-i", "desktop",  # 输入:整个桌面
            # ---------------------- VBR相关编码参数(核心保留)----------------------
            "-c:v", "libx264",  # x264编码器完美支持VBR
            "-preset", "fast",  # 编码速度/质量预设(不影响VBR)
            "-b:v", video_bitrate,  # VBR目标比特率
            "-maxrate", video_maxrate,  # VBR最大比特率上限
            "-bufsize", video_bufsize,  # VBR缓冲区(关键)
            # ----------------------------------------------------------------------
            # 关键:强制恒定帧率+同步策略(解决时长偏差的核心,与VBR兼容)
            "-r", str(framerate),  # 输出帧率强制等于输入帧率
            "-vsync", "cfr",  # 恒定帧率模式:保证时间轴与实际时长一致
            # 输出格式参数(碎片化,确保中断后可播放)
            "-movflags", "+frag_keyframe+empty_moov",  # 碎片化参数
            "-max_muxing_queue_size", "1024",  # 混流队列大小
            "-pix_fmt", "yuv420p",  # 颜色空间(兼容x264)
            "-y",  # 覆盖已存在的文件
            temp_file  # 临时输出文件
        ]

        # 打印FFmpeg命令行(调试用,可查看VBR参数是否生效)
        print(f"\nFFmpeg执行命令:{' '.join(ffmpeg_cmd)}")

        # 启动FFmpeg录屏进程
        recording_process = subprocess.Popen(
            ffmpeg_cmd,
            stdout=subprocess.DEVNULL,  # 丢弃标准输出
            stderr=subprocess.DEVNULL,  # 丢弃标准错误
            stdin=subprocess.PIPE,
            creationflags=subprocess.CREATE_NEW_PROCESS_GROUP  # Windows下创建新进程组
        )

        print("开始录制屏幕...")

        # 等待录制进程结束或收到停止信号(优化:减少sleep时间,提高响应速度)
        while recording_process.poll() is None and not stop_recording:
            time.sleep(0.2)  # 从0.5秒改为0.2秒,减少检查延迟

        # 优雅停止FFmpeg进程(优化:先尝试发送q,再用信号,减少超时)
        if stop_recording and recording_process.poll() is None:
            print("\n正在停止录制并保存视频...")
            try:
                # 第一步:尝试发送q信号(FFmpeg的标准停止指令),非阻塞
                recording_process.stdin.write(b'q\n')
                recording_process.stdin.flush()
                time.sleep(0.5)  # 给FFmpeg一点时间响应
                # 第二步:等待进程退出,缩短超时时间到5秒
                recording_process.wait(timeout=5)
            except (subprocess.TimeoutExpired, BrokenPipeError):
                # 超时或管道错误,再用信号强制停止
                try:
                    if os.name == 'nt':
                        ctypes.windll.kernel32.GenerateConsoleCtrlEvent(0, recording_process.pid)
                    else:
                        recording_process.send_signal(signal.SIGINT)
                    recording_process.wait(timeout=2)
                except:
                    print("FFmpeg未及时响应,强制终止...")
                    recording_process.terminate()
                    recording_process.wait()
            except Exception as e:
                print(f"停止FFmpeg时出错:{e}")
                recording_process.terminate()
                recording_process.wait()

    except Exception as e:
        print(f"\n录制过程中发生错误:{str(e)}")
    finally:
        # 等待一小段时间确保FFmpeg完全退出(缩短sleep时间)
        time.sleep(0.5)
        # 修复时长(如果临时文件存在,修复时保留VBR特征)
        if os.path.exists(temp_file):
            fix_mp4_duration(temp_file, output_file)
        else:
            print("未找到临时文件,录制可能未开始或已删除")

def stop_recording_thread():
    """停止录制线程"""
    global stop_recording, recording_thread
    stop_recording = True

    if recording_thread and recording_thread.is_alive():
        print("等待录制线程结束...")
        recording_thread.join(timeout=10)  # 从15秒改回10秒(优化后无需更长时间)
        if recording_thread.is_alive():
            print("警告:录制线程未在10秒内结束,强制结束")
        else:
            print("录制已完全停止")
    recording_thread = None

def video_run():
    """屏幕录制装饰器"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            global recording_thread, business_thread
            global stop_recording
            stop_recording = False

            # 启动录制线程
            recording_thread = threading.Thread(target=record_screen,daemon=False)
            recording_thread.start()
            time.sleep(0.5)  # 从1秒改为0.5秒,减少初始化延迟

            # 定义业务逻辑执行函数
            def run_business():
                try:
                    func(*args, **kwargs)
                except Exception as e:
                    print(f"\n主线程业务逻辑发生异常:{str(e)}")

            # 启动业务逻辑线程
            business_thread = threading.Thread(target=run_business, daemon=False)
            business_thread.start()

            # 等待业务逻辑结束
            business_thread.join()
            print("\n主线程业务逻辑执行结束,准备停止录制")
            stop_recording_thread()

        return wrapper
    return decorator

# ************************** 主线程业务逻辑 **************************
@video_run()  # 路径使用原始字符串
def main_business():
    """示例业务逻辑"""
    print("主线程业务逻辑开始执行...")
    time.sleep(10)  # 模拟业务执行10秒
    raise RuntimeError("业务逻辑出错了!")  # 测试异常情况
    print("主线程业务逻辑执行完成!")

# ************************** 程序入口 **************************
if __name__ == "__main__":
    main_business()
    print("主线程运行完毕,程序即将退出")
相关推荐
小鸡吃米…2 小时前
Python - 多重继承
开发语言·python
悟能不能悟2 小时前
java list怎么进行group
java·python·list
hssfscv2 小时前
Javeweb学习笔记——Vue+Ajax
vue.js·笔记·学习·ajax
专注于大数据技术栈2 小时前
java学习--Math 类常用方法
java·学习
在等星星呐2 小时前
人工智能从0基础到精通
前端·人工智能·python
世界唯一最大变量2 小时前
自创的机械臂新算法,因为是AI写的,暂时,并不智能,但目前支持任何段数
python·排序算法
C+++Python2 小时前
如何选择合适的锁机制来提高 Java 程序的性能?
java·前端·python
long3162 小时前
类与对象 | 低级别设计 (LLD)
java·spring boot·学习·程序人生·spring·设计模式·学习方法
专注于大数据技术栈2 小时前
java学习--String、StringBuilder、StringBuffer 的核心区别
java·学习