20260109
This commit is contained in:
12
视频分割工作区/.claude/settings.local.json
Normal file
12
视频分割工作区/.claude/settings.local.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(ffmpeg:*)",
|
||||
"Bash(conda create:*)",
|
||||
"Bash(conda run:*)",
|
||||
"Bash(export PYTHONIOENCODING=utf-8)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
}
|
||||
}
|
||||
32
视频分割工作区/gemini会议信息提取提示词.md
Normal file
32
视频分割工作区/gemini会议信息提取提示词.md
Normal file
@ -0,0 +1,32 @@
|
||||
# 任务
|
||||
|
||||
请你对这段会议视频进行信息提取,不做任何总结、分析或加工。按照视频实际发生的时间顺序,逐条依次输出所有事件,包括发言内容和画面变化。
|
||||
|
||||
# 输出要求
|
||||
|
||||
## **1. 逐字语音转写**
|
||||
|
||||
- 务必准确逐字转写全部发言内容,不跳过任何发言信息,从视频开始到视频结束
|
||||
- 按发言者区分
|
||||
- 不用带时间戳,按照时间顺序排列即可
|
||||
|
||||
## **2. 画面内容提取**
|
||||
|
||||
- 在时间顺序中穿插画面信息,与发言一样按时间线记录
|
||||
- 格式统一为:
|
||||
- 内容包括但不限于:
|
||||
- 屏幕共享画面的内容精要
|
||||
- 屏幕切换或场景变化
|
||||
|
||||
# **【最终输出格式(示例)】**
|
||||
|
||||
以下仅示例格式,不代表内容:
|
||||
|
||||
```
|
||||
发言者 1:大家好,我们现在开始会议……
|
||||
画面内容:切换到 PPT,标题为“项目进展”
|
||||
发言者 2:我先介绍一下当前的任务情况……
|
||||
画面内容:Excel 表格出现,包含列“任务”“负责人”“状态”
|
||||
```
|
||||
|
||||
按照这种方式不断顺序输出,从视频开始直到视频结束。
|
||||
224
视频分割工作区/split_video.py
Normal file
224
视频分割工作区/split_video.py
Normal file
@ -0,0 +1,224 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
视频按大小分割脚本
|
||||
先压缩画面质量,再将视频分割为指定时长的片段
|
||||
"""
|
||||
import os
|
||||
import subprocess
|
||||
import json
|
||||
from pathlib import Path
|
||||
import imageio_ffmpeg
|
||||
|
||||
# 配置
|
||||
INPUT_DIR = Path(__file__).parent / "原视频"
|
||||
OUTPUT_DIR = Path(__file__).parent / "分割后"
|
||||
TEMP_DIR = Path(__file__).parent / "压缩临时"
|
||||
TARGET_DURATION_MIN = 50 # 目标片段时长(分钟)
|
||||
MERGE_THRESHOLD_MIN = 10 # 最后一段小于此时长则合并到前一段
|
||||
COMPRESS_CRF = 51 # 画面压缩质量 (0-51, 越大压缩越狠)
|
||||
FFMPEG_PATH = imageio_ffmpeg.get_ffmpeg_exe()
|
||||
|
||||
def get_video_info(video_path):
|
||||
"""获取视频信息:时长和文件大小(快速,只读取头部)"""
|
||||
import re
|
||||
# 只读取文件头部信息,不处理整个视频
|
||||
cmd = [
|
||||
FFMPEG_PATH,
|
||||
'-i', str(video_path),
|
||||
]
|
||||
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, encoding='utf-8')
|
||||
# 从stderr中解析时长 (ffmpeg输出信息在stderr)
|
||||
duration_match = re.search(r'Duration: (\d+):(\d+):(\d+\.?\d*)', result.stderr)
|
||||
if duration_match:
|
||||
h, m, s = duration_match.groups()
|
||||
duration = int(h) * 3600 + int(m) * 60 + float(s)
|
||||
else:
|
||||
raise ValueError("无法获取视频时长")
|
||||
|
||||
file_size = os.path.getsize(video_path)
|
||||
return duration, file_size
|
||||
|
||||
def compress_video(video_path, output_path):
|
||||
"""压缩视频画面质量,音频保持原样"""
|
||||
# 先获取视频时长用于进度计算
|
||||
duration, _ = get_video_info(video_path)
|
||||
|
||||
cmd = [
|
||||
FFMPEG_PATH,
|
||||
'-y',
|
||||
'-i', str(video_path),
|
||||
'-c:v', 'libx264', # CPU编码
|
||||
'-crf', str(COMPRESS_CRF), # 质量控制 (0-51)
|
||||
'-preset', 'ultrafast', # 最快编码速度
|
||||
'-c:a', 'copy', # 音频直接复制,不重新编码
|
||||
str(output_path)
|
||||
]
|
||||
|
||||
import time as time_module
|
||||
import threading
|
||||
|
||||
print(f" 压缩中...", end='', flush=True)
|
||||
|
||||
# 用线程执行 ffmpeg
|
||||
result = {'returncode': None, 'stderr': ''}
|
||||
|
||||
def run_ffmpeg():
|
||||
proc = subprocess.run(cmd, capture_output=True)
|
||||
result['returncode'] = proc.returncode
|
||||
result['stderr'] = proc.stderr.decode('utf-8', errors='replace')
|
||||
|
||||
thread = threading.Thread(target=run_ffmpeg)
|
||||
thread.start()
|
||||
|
||||
start_time = time_module.time()
|
||||
while thread.is_alive():
|
||||
time_module.sleep(1)
|
||||
elapsed = int(time_module.time() - start_time)
|
||||
if output_path.exists():
|
||||
current_size = os.path.getsize(output_path) / (1024 * 1024)
|
||||
print(f"\r 压缩中... 已用{elapsed}秒, 输出{current_size:.1f}MB", end='', flush=True)
|
||||
|
||||
thread.join()
|
||||
elapsed = int(time_module.time() - start_time)
|
||||
|
||||
# 检查是否成功
|
||||
if result['returncode'] != 0:
|
||||
print(f"\r 压缩失败! 错误码: {result['returncode']}", flush=True)
|
||||
print(f" 错误信息: {result['stderr'][-500:]}", flush=True)
|
||||
raise RuntimeError("压缩失败")
|
||||
|
||||
print(f"\r 压缩完成! 耗时{elapsed}秒 ", flush=True)
|
||||
return output_path
|
||||
|
||||
def split_video(video_path, original_name=None):
|
||||
"""将视频按时长分割"""
|
||||
video_path = Path(video_path)
|
||||
video_name = original_name if original_name else video_path.stem
|
||||
video_ext = video_path.suffix
|
||||
|
||||
# 获取视频信息
|
||||
duration, file_size = get_video_info(video_path)
|
||||
file_size_mb = file_size / (1024 * 1024)
|
||||
duration_min = duration / 60
|
||||
|
||||
print(f"处理: {video_path.name}", flush=True)
|
||||
print(f" 时长: {duration_min:.1f}分钟, 大小: {file_size_mb:.1f}MB", flush=True)
|
||||
|
||||
target_duration = TARGET_DURATION_MIN * 60 # 转为秒
|
||||
merge_threshold = MERGE_THRESHOLD_MIN * 60 # 转为秒
|
||||
|
||||
# 如果视频短于目标时长,直接复制
|
||||
if duration <= target_duration:
|
||||
print(f" 视频短于{TARGET_DURATION_MIN}分钟,无需分割")
|
||||
output_path = OUTPUT_DIR / f"1-{video_name}{video_ext}"
|
||||
import shutil
|
||||
shutil.copy2(video_path, output_path)
|
||||
print(f" 已复制到: {output_path.name}")
|
||||
return [output_path]
|
||||
|
||||
# 计算片段时间点
|
||||
segments = []
|
||||
start_time = 0
|
||||
while start_time < duration:
|
||||
remaining = duration - start_time
|
||||
if remaining <= target_duration + merge_threshold:
|
||||
# 剩余时间不多,作为最后一段
|
||||
segments.append((start_time, remaining))
|
||||
break
|
||||
else:
|
||||
segments.append((start_time, target_duration))
|
||||
start_time += target_duration
|
||||
|
||||
print(f" 分割为 {len(segments)} 个片段,每段约 {TARGET_DURATION_MIN} 分钟", flush=True)
|
||||
|
||||
output_files = []
|
||||
for segment_index, (start_time, actual_duration) in enumerate(segments, 1):
|
||||
output_path = OUTPUT_DIR / f"{segment_index}-{video_name}{video_ext}"
|
||||
|
||||
cmd = [
|
||||
FFMPEG_PATH,
|
||||
'-y', # 覆盖输出文件
|
||||
'-i', str(video_path),
|
||||
'-ss', str(start_time),
|
||||
'-t', str(actual_duration),
|
||||
'-c', 'copy', # 不重新编码,速度快
|
||||
'-avoid_negative_ts', 'make_zero',
|
||||
str(output_path)
|
||||
]
|
||||
|
||||
print(f" 分割片段 {segment_index}: {start_time/60:.1f}min - {(start_time + actual_duration)/60:.1f}min", flush=True)
|
||||
|
||||
subprocess.run(cmd, capture_output=True, encoding='utf-8')
|
||||
|
||||
# 检查输出文件大小
|
||||
if output_path.exists():
|
||||
out_size = os.path.getsize(output_path) / (1024 * 1024)
|
||||
print(f" -> {output_path.name} ({out_size:.1f}MB, {actual_duration/60:.1f}min)")
|
||||
output_files.append(output_path)
|
||||
|
||||
return output_files
|
||||
|
||||
def main():
|
||||
print("=" * 50)
|
||||
print("视频压缩+分割工具")
|
||||
print(f"输入目录: {INPUT_DIR}")
|
||||
print(f"输出目录: {OUTPUT_DIR}")
|
||||
print(f"画面压缩: CRF {COMPRESS_CRF}")
|
||||
print(f"目标时长: {TARGET_DURATION_MIN}分钟/段")
|
||||
print("=" * 50)
|
||||
|
||||
# 创建目录
|
||||
OUTPUT_DIR.mkdir(exist_ok=True)
|
||||
TEMP_DIR.mkdir(exist_ok=True)
|
||||
|
||||
# 支持的视频格式
|
||||
video_extensions = {'.mp4', '.avi', '.mkv', '.mov', '.wmv', '.flv', '.webm'}
|
||||
|
||||
# 获取所有视频文件
|
||||
video_files = [f for f in INPUT_DIR.iterdir()
|
||||
if f.is_file() and f.suffix.lower() in video_extensions]
|
||||
|
||||
if not video_files:
|
||||
print("未找到视频文件!")
|
||||
return
|
||||
|
||||
print(f"找到 {len(video_files)} 个视频文件\n")
|
||||
|
||||
# 处理每个视频
|
||||
for video_file in video_files:
|
||||
try:
|
||||
# 第一步:压缩视频
|
||||
original_size = os.path.getsize(video_file) / (1024 * 1024)
|
||||
print(f"压缩: {video_file.name} ({original_size:.1f}MB)", flush=True)
|
||||
|
||||
compressed_path = TEMP_DIR / f"{video_file.stem}_compressed.mp4"
|
||||
compress_video(video_file, compressed_path)
|
||||
|
||||
if compressed_path.exists():
|
||||
compressed_size = os.path.getsize(compressed_path) / (1024 * 1024)
|
||||
ratio = (1 - compressed_size / original_size) * 100
|
||||
print(f" -> 压缩后: {compressed_size:.1f}MB (压缩率 {ratio:.1f}%)", flush=True)
|
||||
|
||||
# 第二步:分割压缩后的视频
|
||||
split_video(compressed_path, original_name=video_file.stem)
|
||||
|
||||
# 清理临时文件
|
||||
if compressed_path.exists():
|
||||
compressed_path.unlink()
|
||||
print()
|
||||
except Exception as e:
|
||||
print(f" 错误: {e}\n")
|
||||
|
||||
# 清理临时目录
|
||||
try:
|
||||
TEMP_DIR.rmdir()
|
||||
except:
|
||||
pass
|
||||
|
||||
print("=" * 50)
|
||||
print("处理完成!")
|
||||
print("=" * 50)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
17
视频分割工作区/一键分割视频.bat
Normal file
17
视频分割工作区/一键分割视频.bat
Normal file
@ -0,0 +1,17 @@
|
||||
@echo off
|
||||
echo ================================================
|
||||
echo Video Split Tool
|
||||
echo ================================================
|
||||
echo.
|
||||
|
||||
cd /d "%~dp0"
|
||||
|
||||
call C:\Users\10120\anaconda3\Scripts\activate.bat video_split
|
||||
python "%~dp0split_video.py"
|
||||
|
||||
echo.
|
||||
echo ================================================
|
||||
echo Done
|
||||
echo ================================================
|
||||
echo.
|
||||
pause
|
||||
38
视频分割工作区/裁剪前30分钟.py
Normal file
38
视频分割工作区/裁剪前30分钟.py
Normal file
@ -0,0 +1,38 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
裁剪视频:丢弃前30分钟,保留后面部分
|
||||
"""
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
import imageio_ffmpeg
|
||||
|
||||
FFMPEG_PATH = imageio_ffmpeg.get_ffmpeg_exe()
|
||||
|
||||
# 输入输出(直接覆盖原文件)
|
||||
input_file = Path(r"D:\AA_Work\AIEC-团队开发规范Skills\视频分割\原视频\20251209135052-信通院云大所市场部-张媛媛预定的会议-视频-1.mp4")
|
||||
temp_file = input_file.with_suffix('.tmp.mp4')
|
||||
|
||||
# 丢弃前30分钟
|
||||
skip_seconds = 30 * 60 # 30分钟
|
||||
|
||||
cmd = [
|
||||
FFMPEG_PATH,
|
||||
'-y',
|
||||
'-ss', str(skip_seconds), # 跳过前30分钟
|
||||
'-i', str(input_file),
|
||||
'-c', 'copy', # 不重新编码,速度快
|
||||
'-avoid_negative_ts', 'make_zero',
|
||||
str(temp_file)
|
||||
]
|
||||
|
||||
print(f"文件: {input_file.name}")
|
||||
print(f"跳过前 30 分钟,裁剪中...")
|
||||
|
||||
subprocess.run(cmd)
|
||||
|
||||
# 替换原文件
|
||||
import os
|
||||
os.remove(input_file)
|
||||
temp_file.rename(input_file)
|
||||
|
||||
print("完成! 原文件已更新")
|
||||
Reference in New Issue
Block a user