Python---批量去视频的片头和片尾
在需要对视频进行批量处理的时候,常见的就是批量去视频的片头和片尾:
具体代码示例:
import os import re import time import subprocess from decimal import Decimal from multiprocessing import Pool path = r'E:\FYZ2025\deleteAD\原视频' # 原文件夹路径 new_path = r'E:\FYZ2025\deleteAD\新视频' # 新文件夹路径 if not os.path.exists(new_path): os.mkdir(new_path) else: pass # 获取视频的 duration 时长 长 宽 def get_video_length(file): process = subprocess.Popen(['ffmpeg', '-i', file], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) stdout, stderr = process.communicate() # print(stdout) pattern_duration = re.compile(r"Duration:\s(\d+?):(\d+?):(\d+\.\d+?),") pattern_size = re.compile(r",\s(\d{3,4})x(\d{3,4})[\s or ,]") matches = re.search(pattern_duration, stdout.decode('utf-8')) size = re.search(pattern_size, stdout.decode('utf-8')) if size: size = size.groups() # print(size) if matches: matches = matches.groups() # print(matches) hours = Decimal(matches[0]) minutes = Decimal(matches[1]) seconds = Decimal(matches[2]) # 处理为十进制,避免小数点报错 total = 0 total += 60 * 60 * hours total += 60 * minutes total += seconds width = size[0] height = size[1] return {'total': total, 'width': width, 'height': height} def cutVideo(startPoint, file, endPoint, newFile): command = [ 'ffmpeg', '-ss', startPoint, '-i', file, '-acodec', 'copy', '-vcodec', 'copy', '-t', endPoint, newFile ] subprocess.call(command) def millisecToAssFormat(t): # 取时间,单位秒 s = t % 60 m = t // 60 if t < 3600: h = 00 else: h = t // 3600 m = t // 60 - h * 60 return '%02d:%02d:%02d' % (h, m, s) def main(dict): file = dict['file'] # 文件名 piantou = dict['piantou'] # 片头时长 pianwei = dict['pianwei'] # 片尾时长 videoInfo = get_video_length(file) # 视频信息 # print(videoInfo) if videoInfo: duration = videoInfo.get('total') # 时长 秒 startPoint = piantou # 剪掉片头时间,从原文件此处开始播放 startPoint = millisecToAssFormat(startPoint) endPoint = duration - piantou - pianwei # 剪掉片头片尾时间和,结果等于新文件总时长 endPoint = millisecToAssFormat(endPoint) new_File = new_path + file.replace(path, '') # 创建生成的文件路径+文件名 # print(new_File, endPoint) cutVideo(startPoint, file, endPoint, new_File) if __name__ == '__main__': # main() timt0 = time.time() file = [os.path.join(path, file) for file in os.listdir(path)] a = input('输入片头时长:') # 片头时长 b = input('输入片尾时长:') # 片尾时长 piantou = [int(a)] * len(file) # 片头时长列表 pianwei = [int(b)] * len(file) # 片尾时长列表 dict_list = [] # main函数参数列表 for x in range(len(file)): dict = { 'file': file[x], 'piantou': piantou[x], 'pianwei': pianwei[x] } # main函数参数,文件路径,片头片尾时长 dict_list.append(dict) print('\n规则:去除片头%s秒片尾%s秒,开始。\n' % (a, b)) pool = Pool() pool.map(main, dict_list) pool.close() pool.join() time1 = time.time() - timt0 print('\n结束,处理%d个视频文件,共用时%.4f秒。\n' % (len(file), time1)) input('回车键退出')
这个代码是基础的代码,片头和片尾的时长是秒。
但是我实际使用过程中,浮点的小数秒。
经过豆包AI的优化:
import os import re import time import subprocess from decimal import Decimal from multiprocessing import Pool from typing import List, Dict # 配置路径(建议用os.path.join统一处理路径分隔符) path = r'C:\2025电商美工\原视频' new_path = r'C:\2025电商美工\新视频' os.makedirs(new_path, exist_ok=True) # 简化文件夹创建逻辑 # 支持的视频格式(可根据需求扩展) SUPPORTED_FORMATS = ('.mp4', '.avi', '.mov', '.mkv', '.flv', '.wmv') # 验证ffmpeg是否可用 def check_ffmpeg() -> bool: try: subprocess.run(['ffmpeg', '-version'], capture_output=True, check=True) return True except (subprocess.CalledProcessError, FileNotFoundError): print("错误:未找到ffmpeg!请先安装并配置环境变量(参考之前的指导)") return False # 获取视频信息(优化正则+错误处理) def get_video_length(file: str) -> Dict[str, Decimal | str] | None: try: process = subprocess.Popen( ['ffmpeg', '-i', file], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, encoding='utf-8', # 直接指定编码,避免手动decode errors='ignore' # 忽略编码错误 ) stdout, _ = process.communicate(timeout=30) # 设置超时,避免卡死 except Exception as e: print(f"获取视频信息失败:{file},错误:{str(e)}") return None # 优化正则:放宽匹配条件,适应不同ffmpeg版本 pattern_duration = re.compile(r"Duration:\s*(\d+):(\d+):(\d+\.\d+)", re.IGNORECASE) pattern_size = re.compile(r"(\d{3,4})x(\d{3,4})", re.IGNORECASE) duration_match = pattern_duration.search(stdout) size_match = pattern_size.search(stdout) if not (duration_match and size_match): print(f"无法提取视频信息:{file}(可能不是支持的视频格式)") return None # 解析时长 hours = Decimal(duration_match.group(1)) minutes = Decimal(duration_match.group(2)) seconds = Decimal(duration_match.group(3)) total_seconds = hours * 3600 + minutes * 60 + seconds # 解析分辨率 width, height = size_match.groups() return { 'total': total_seconds, 'width': width, 'height': height } # 裁剪视频(优化命令+错误处理) def cutVideo(startPoint: str, file: str, endPoint: str, newFile: str) -> None: # 确保输出目录存在(处理子文件夹场景) output_dir = os.path.dirname(newFile) os.makedirs(output_dir, exist_ok=True) command = [ 'ffmpeg', '-ss', startPoint, '-i', file, '-acodec', 'copy', '-vcodec', 'copy', '-t', endPoint, '-y', # -y:覆盖已存在的输出文件 newFile ] try: # 捕获ffmpeg输出,便于调试 result = subprocess.run( command, capture_output=True, encoding='utf-8', errors='ignore', timeout=300 ) if result.returncode != 0: print(f"裁剪失败:{file},错误信息:{result.stderr[:500]}") # 只打印前500字符 else: print(f"裁剪成功:{newFile}") except Exception as e: print(f"裁剪异常:{file},错误:{str(e)}") # 时间格式转换(支持小数秒,保留3位精度) def secToFFmpegFormat(t: float) -> str: if t < 0: return "00:00:00.000" hours = int(t // 3600) minutes = int((t % 3600) // 60) seconds = t % 60 return f"{hours:02d}:{minutes:02d}:{seconds:06.3f}" # 格式:hh:mm:ss.sss # 输入验证函数 def get_valid_duration(prompt: str) -> float | None: while True: user_input = input(prompt).strip() try: duration = float(user_input) if duration >= 0: return duration else: print("错误:时长不能为负数,请重新输入!") except ValueError: print("错误:请输入有效的数字(支持小数,如2.5)!") # 主处理函数 def main(task: Dict[str, str | float]) -> None: file = task['file'] piantou = task['piantou'] pianwei = task['pianwei'] # 获取视频信息 videoInfo = get_video_length(file) if not videoInfo: return duration = videoInfo['total'] # Decimal类型 total_cut = piantou + pianwei # float类型 # 关键修改:统一数据类型为Decimal,避免运算错误 total_cut_decimal = Decimal(str(total_cut)) # 先转字符串再转Decimal,保证精度 # 验证裁剪时长是否合理 if total_cut_decimal >= duration: print(f"跳过:{file}(片头+片尾时长{total_cut:.1f}秒 ≥ 视频总时长{duration:.1f}秒)") return # 计算裁剪参数 startPoint = secToFFmpegFormat(piantou) endPoint_seconds = duration - total_cut_decimal # Decimal类型运算 endPoint = secToFFmpegFormat(float(endPoint_seconds)) # 转为float适配时间格式函数 # 生成输出路径(优化路径拼接逻辑) relative_path = os.path.relpath(file, path) new_File = os.path.join(new_path, relative_path) # 执行裁剪 cutVideo(startPoint, file, endPoint, new_File) # 递归获取所有支持的视频文件 def get_all_video_files(folder: str) -> List[str]: video_files = [] for root, _, files in os.walk(folder): for file in files: if file.lower().endswith(SUPPORTED_FORMATS): video_files.append(os.path.join(root, file)) return video_files if __name__ == '__main__': # 检查ffmpeg可用性 if not check_ffmpeg(): input("按回车键退出...") exit(1) # 获取所有视频文件 video_files = get_all_video_files(path) if not video_files: print(f"未在{path}找到支持的视频文件(支持格式:{SUPPORTED_FORMATS})") input("按回车键退出...") exit(0) print(f"共找到{len(video_files)}个视频文件,准备处理...") # 获取用户输入的时长(带验证) piantou_duration = get_valid_duration("输入片头时长(秒,支持小数):") pianwei_duration = get_valid_duration("输入片尾时长(秒,支持小数):") if piantou_duration is None or pianwei_duration is None: exit(1) # 构建任务列表 dict_list = [ { 'file': file, 'piantou': piantou_duration, 'pianwei': pianwei_duration } for file in video_files ] # 多进程处理(限制进程数为CPU核心数的一半,避免资源占用过高) print(f"\n规则:去除片头{piantou_duration}秒、片尾{pianwei_duration}秒,开始处理...\n") timt0 = time.time() with Pool(processes=os.cpu_count() // 2) as pool: # 优雅的进程池管理 pool.map(main, dict_list) # 输出统计信息 time1 = time.time() - timt0 print(f"\n处理完成!共处理{len(video_files)}个视频文件,总耗时{time1:.4f}秒。") input("按回车键退出...")
简直了YYDS!
打完收工!

浙公网安备 33010602011771号