将字幕提取时间轴逻辑改为正常;新增合并视频片段模版输出为json文件,id和时间轴对齐。尚未完成:如果llm输出的json格式错误,则合并失败
This commit is contained in:
parent
2079383951
commit
7636eca330
@ -229,7 +229,93 @@ def save_result_to_txt(response_text, video_path, video_dir, save_dir="/root/aut
|
|||||||
print(f"\n❌ 保存TXT文件失败: {e}")
|
print(f"\n❌ 保存TXT文件失败: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def save_result_to_txt(response_text, video_path, video_dir, save_dir="/root/autodl-tmp/video_llm"):
|
||||||
|
"""将分析结果保存为TXT文件"""
|
||||||
|
# 创建保存目录
|
||||||
|
os.makedirs(save_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# 生成文件名(基于视频文件名和时间戳)
|
||||||
|
video_name = os.path.splitext(os.path.basename(video_path))[0]
|
||||||
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
|
txt_filename = f"{video_dir}_{timestamp}.txt"
|
||||||
|
txt_dir = os.path.join(save_dir, "Template", video_name)
|
||||||
|
os.makedirs(txt_dir, exist_ok=True)
|
||||||
|
txt_path = os.path.join(txt_dir, txt_filename)
|
||||||
|
# 准备保存内容(添加头部信息)
|
||||||
|
content = f"""视频分析结果
|
||||||
|
=====================================
|
||||||
|
视频文件: {video_path}
|
||||||
|
分析时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
||||||
|
=====================================
|
||||||
|
|
||||||
|
{response_text}
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 保存到文件
|
||||||
|
try:
|
||||||
|
with open(txt_path, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(content)
|
||||||
|
print(f"\n✅ 分析结果已保存到: {txt_path}")
|
||||||
|
return txt_path
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\n❌ 保存TXT文件失败: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def save_result_to_json(response_text, base_dir, video_dir):
|
||||||
|
"""将分析结果保存为JSON文件"""
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
|
||||||
|
# 创建保存目录
|
||||||
|
json_dir = os.path.join(base_dir, "llm")
|
||||||
|
os.makedirs(json_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# 生成文件名
|
||||||
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
|
json_filename = f"{video_dir}_{timestamp}.json"
|
||||||
|
json_path = os.path.join(json_dir, json_filename)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 尝试从响应文本中提取JSON内容
|
||||||
|
# 查找JSON格式的内容(通常在```json和```之间,或者直接是JSON格式)
|
||||||
|
json_match = re.search(r'```json\s*(.*?)\s*```', response_text, re.DOTALL)
|
||||||
|
if json_match:
|
||||||
|
json_content = json_match.group(1)
|
||||||
|
else:
|
||||||
|
# 如果没有找到```json标记,尝试直接解析整个响应
|
||||||
|
json_content = response_text
|
||||||
|
|
||||||
|
# 尝试解析JSON
|
||||||
|
try:
|
||||||
|
parsed_json = json.loads(json_content)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
# 如果解析失败,将整个响应作为文本保存
|
||||||
|
parsed_json = {
|
||||||
|
"raw_response": response_text,
|
||||||
|
"analysis_time": datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
||||||
|
"video_dir": video_dir,
|
||||||
|
"note": "JSON解析失败,保存原始响应"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 保存到文件
|
||||||
|
with open(json_path, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(parsed_json, f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
print(f"\n✅ JSON结果已保存到: {json_path}")
|
||||||
|
return json_path
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\n❌ 保存JSON文件失败: {e}")
|
||||||
|
# 如果保存失败,尝试保存原始文本
|
||||||
|
try:
|
||||||
|
fallback_path = json_path.replace('.json', '_raw.txt')
|
||||||
|
with open(fallback_path, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(response_text)
|
||||||
|
print(f"\n⚠️ 原始响应已保存到: {fallback_path}")
|
||||||
|
return fallback_path
|
||||||
|
except Exception as e2:
|
||||||
|
print(f"\n❌ 保存原始响应也失败: {e2}")
|
||||||
|
return None
|
||||||
|
|
||||||
# 只有在直接运行此文件时才执行以下代码
|
# 只有在直接运行此文件时才执行以下代码
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@ -223,9 +223,11 @@ for i ,video_dir in enumerate(video_dirs):
|
|||||||
print(full_response)
|
print(full_response)
|
||||||
|
|
||||||
# 保存结果为TXT文件
|
# 保存结果为TXT文件
|
||||||
txt_file_path = save_result_to_txt(full_response, base_dir , video_dir)
|
txt_file_path = save_result_to_txt(full_response, base_dir, video_dir)
|
||||||
|
# 保存结果为JSON文件
|
||||||
|
json_file_path = save_result_to_json(full_response, base_dir, video_dir)
|
||||||
# 保存使用情况信息
|
# 保存使用情况信息
|
||||||
usage_info_txt = save_usage_info_to_txt(usage_info, total_duration, money, base_dir , video_dir)
|
usage_info_txt = save_usage_info_to_txt(usage_info, total_duration, money, base_dir, video_dir)
|
||||||
|
|
||||||
# 输出使用情况信息
|
# 输出使用情况信息
|
||||||
if usage_info:
|
if usage_info:
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import re
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import logging
|
import logging
|
||||||
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||||
|
|
||||||
# 添加当前目录到路径以导入OCR模块
|
# 添加当前目录到路径以导入OCR模块
|
||||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||||
@ -60,32 +61,7 @@ class BatchSubtitleExtractor:
|
|||||||
return int(match.group(1))
|
return int(match.group(1))
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
def _adjust_timestamps(self, results, segment_number):
|
|
||||||
"""
|
|
||||||
根据片段号调整时间戳
|
|
||||||
|
|
||||||
Args:
|
|
||||||
results: OCR结果字典
|
|
||||||
segment_number: 片段号
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
dict: 调整后的结果
|
|
||||||
"""
|
|
||||||
if segment_number == 1:
|
|
||||||
# 片段号为1,不调整时间
|
|
||||||
return results
|
|
||||||
|
|
||||||
# 计算时间偏移量
|
|
||||||
time_offset = (segment_number - 1) * 30
|
|
||||||
|
|
||||||
logger.info(f"片段号: {segment_number}, 时间偏移: +{time_offset}秒")
|
|
||||||
|
|
||||||
# 调整所有字幕的时间戳
|
|
||||||
for subtitle in results['subtitles']:
|
|
||||||
if 'timestamp' in subtitle:
|
|
||||||
subtitle['timestamp'] += time_offset
|
|
||||||
|
|
||||||
return results
|
|
||||||
|
|
||||||
def find_video_files(self, input_dir):
|
def find_video_files(self, input_dir):
|
||||||
"""查找目录中的所有视频文件"""
|
"""查找目录中的所有视频文件"""
|
||||||
@ -188,8 +164,7 @@ class BatchSubtitleExtractor:
|
|||||||
subtitle_position=kwargs.get('position', 'bottom')
|
subtitle_position=kwargs.get('position', 'bottom')
|
||||||
)
|
)
|
||||||
|
|
||||||
# 根据片段号调整时间戳
|
|
||||||
results = self._adjust_timestamps(results, segment_number)
|
|
||||||
|
|
||||||
# 创建OCR目录
|
# 创建OCR目录
|
||||||
video_ocr_dir.mkdir(parents=True, exist_ok=True)
|
video_ocr_dir.mkdir(parents=True, exist_ok=True)
|
||||||
@ -239,19 +214,20 @@ class BatchSubtitleExtractor:
|
|||||||
'segment_number': segment_number
|
'segment_number': segment_number
|
||||||
}
|
}
|
||||||
|
|
||||||
def extract_batch(self, input_dir, output_dir, **kwargs):
|
def extract_batch(self, input_dir, output_dir, max_workers=4, **kwargs):
|
||||||
"""
|
"""
|
||||||
批量提取字幕(串行处理)
|
批量提取字幕(并行处理)
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
input_dir: 输入目录或文件
|
input_dir: 输入目录或文件
|
||||||
output_dir: 输出目录
|
output_dir: 输出目录
|
||||||
|
max_workers: 最大并行工作线程数
|
||||||
**kwargs: 其他参数
|
**kwargs: 其他参数
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: 批量处理结果
|
dict: 批量处理结果
|
||||||
"""
|
"""
|
||||||
logger.info(f"开始批量字幕提取(串行处理)")
|
logger.info(f"开始批量字幕提取(并行处理,最大线程数: {max_workers})")
|
||||||
logger.info(f"输入: {input_dir}")
|
logger.info(f"输入: {input_dir}")
|
||||||
logger.info(f"输出目录: {output_dir}")
|
logger.info(f"输出目录: {output_dir}")
|
||||||
logger.info(f"OCR引擎: {self.ocr_engine}")
|
logger.info(f"OCR引擎: {self.ocr_engine}")
|
||||||
@ -274,16 +250,39 @@ class BatchSubtitleExtractor:
|
|||||||
logger.info(f"找到 {len(video_files)} 个视频文件")
|
logger.info(f"找到 {len(video_files)} 个视频文件")
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
|
completed_count = 0
|
||||||
|
|
||||||
# 串行处理所有视频文件
|
# 并行处理所有视频文件
|
||||||
for i, video_file in enumerate(video_files, 1):
|
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
||||||
logger.info(f"处理第 {i}/{len(video_files)} 个视频")
|
# 提交所有任务
|
||||||
result = self.extract_single_video(video_file, output_dir, **kwargs)
|
future_to_video = {
|
||||||
results.append(result)
|
executor.submit(self.extract_single_video, video_file, output_dir, **kwargs): video_file
|
||||||
|
for video_file in video_files
|
||||||
|
}
|
||||||
|
|
||||||
# 显示进度
|
# 处理完成的任务
|
||||||
progress = i / len(video_files) * 100
|
for future in as_completed(future_to_video):
|
||||||
logger.info(f"批量处理进度: {progress:.1f}% ({i}/{len(video_files)})")
|
video_file = future_to_video[future]
|
||||||
|
completed_count += 1
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = future.result()
|
||||||
|
results.append(result)
|
||||||
|
|
||||||
|
# 显示进度
|
||||||
|
progress = completed_count / len(video_files) * 100
|
||||||
|
logger.info(f"批量处理进度: {progress:.1f}% ({completed_count}/{len(video_files)}) - {video_file.name}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = f"处理视频 {video_file} 时出错: {str(e)}"
|
||||||
|
logger.error(error_msg)
|
||||||
|
results.append({
|
||||||
|
'video_path': str(video_file),
|
||||||
|
'success': False,
|
||||||
|
'error': error_msg,
|
||||||
|
'process_time': 0,
|
||||||
|
'segment_number': self._extract_segment_number(video_file.stem)
|
||||||
|
})
|
||||||
|
|
||||||
total_time = time.time() - start_time
|
total_time = time.time() - start_time
|
||||||
|
|
||||||
@ -357,6 +356,8 @@ def main():
|
|||||||
parser.add_argument("--position", default="full",
|
parser.add_argument("--position", default="full",
|
||||||
choices=["full", "center", "bottom"],
|
choices=["full", "center", "bottom"],
|
||||||
help="字幕区域位置 (full=全屏, center=居中0.5-0.8, bottom=居下0.7-1.0)")
|
help="字幕区域位置 (full=全屏, center=居中0.5-0.8, bottom=居下0.7-1.0)")
|
||||||
|
parser.add_argument("-w", "--workers", type=int, default=4,
|
||||||
|
help="并行处理线程数 (默认: 4)")
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
@ -371,6 +372,7 @@ def main():
|
|||||||
result = batch_extractor.extract_batch(
|
result = batch_extractor.extract_batch(
|
||||||
input_dir=args.input,
|
input_dir=args.input,
|
||||||
output_dir=args.output,
|
output_dir=args.output,
|
||||||
|
max_workers=args.workers,
|
||||||
interval=args.interval,
|
interval=args.interval,
|
||||||
confidence=args.confidence,
|
confidence=args.confidence,
|
||||||
formats=args.formats,
|
formats=args.formats,
|
||||||
|
|||||||
179
code/merge_segment_template.py
Normal file
179
code/merge_segment_template.py
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
视频片段整合脚本
|
||||||
|
用于整合多个视频分析片段文件到一个完整的分析结果
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import glob
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
def extract_json_from_file(file_path):
|
||||||
|
"""从文件中提取JSON内容"""
|
||||||
|
try:
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# 查找JSON内容的开始和结束位置
|
||||||
|
start_marker = "```json"
|
||||||
|
end_marker = "```"
|
||||||
|
|
||||||
|
start_pos = content.find(start_marker)
|
||||||
|
if start_pos == -1:
|
||||||
|
print(f"警告: 在文件 {file_path} 中未找到JSON标记")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 找到JSON内容的开始位置
|
||||||
|
json_start = content.find('{', start_pos)
|
||||||
|
if json_start == -1:
|
||||||
|
print(f"警告: 在文件 {file_path} 中未找到JSON对象")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 找到JSON内容的结束位置
|
||||||
|
json_end = content.rfind(end_marker)
|
||||||
|
if json_end == 0:
|
||||||
|
print(f"警告: 在文件 {file_path} 中未找到JSON对象结束")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 提取JSON字符串
|
||||||
|
json_str = content[json_start:json_end]
|
||||||
|
print(f"json_str:{json_str}")
|
||||||
|
|
||||||
|
# 解析JSON
|
||||||
|
try:
|
||||||
|
json_data = json.loads(json_str)
|
||||||
|
return json_data
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
print(f"错误: 解析文件 {file_path} 中的JSON失败: {e}")
|
||||||
|
print(f"尝试手动修复JSON...")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"错误: 读取文件 {file_path} 失败: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def merge_segments(segment_files):
|
||||||
|
"""合并多个片段文件"""
|
||||||
|
merged_data = {
|
||||||
|
"total_Oral broadcasting": "",
|
||||||
|
"summary": "",
|
||||||
|
"content": []
|
||||||
|
}
|
||||||
|
|
||||||
|
# 按文件名排序,确保按正确顺序合并
|
||||||
|
segment_files.sort()
|
||||||
|
|
||||||
|
for i, file_path in enumerate(segment_files):
|
||||||
|
print(f"正在处理片段 {i+1}: {os.path.basename(file_path)}")
|
||||||
|
|
||||||
|
json_data = extract_json_from_file(file_path)
|
||||||
|
if json_data is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 合并口播内容
|
||||||
|
if "total_Oral broadcasting" in json_data:
|
||||||
|
if merged_data["total_Oral broadcasting"]:
|
||||||
|
merged_data["total_Oral broadcasting"] += " " + json_data["total_Oral broadcasting"]
|
||||||
|
else:
|
||||||
|
merged_data["total_Oral broadcasting"] = json_data["total_Oral broadcasting"]
|
||||||
|
|
||||||
|
# 合并摘要
|
||||||
|
if "summary" in json_data:
|
||||||
|
if merged_data["summary"]:
|
||||||
|
merged_data["summary"] += " " + json_data["summary"]
|
||||||
|
else:
|
||||||
|
merged_data["summary"] = json_data["summary"]
|
||||||
|
|
||||||
|
# 合并内容
|
||||||
|
if "content" in json_data:
|
||||||
|
if i >= 1:
|
||||||
|
for item in json_data["content"]:
|
||||||
|
item["start"] = item["start"] + i*30
|
||||||
|
item["end"] = item["end"] + i*30
|
||||||
|
# 为每个片段的content重新编号
|
||||||
|
for item in json_data["content"]:
|
||||||
|
item["id"] = len(merged_data["content"]) + 1
|
||||||
|
merged_data["content"].append(item)
|
||||||
|
|
||||||
|
return merged_data
|
||||||
|
|
||||||
|
def save_merged_result(merged_data, output_file):
|
||||||
|
"""保存合并结果到JSON文件"""
|
||||||
|
try:
|
||||||
|
# # 添加元数据到JSON中
|
||||||
|
# json_data = {
|
||||||
|
# "metadata": {
|
||||||
|
# "video_file": "/root/autodl-tmp/video_processed/广州广之旅国际旅行社股份有限公司",
|
||||||
|
# "analysis_time": datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
||||||
|
# "merge_time": datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
# },
|
||||||
|
# "data": merged_data
|
||||||
|
# }
|
||||||
|
|
||||||
|
# 保存为纯JSON文件
|
||||||
|
with open(output_file, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(merged_data, f, ensure_ascii=False, indent=4)
|
||||||
|
|
||||||
|
print(f"合并结果已保存到: {output_file}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"错误: 保存合并结果失败: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""主函数"""
|
||||||
|
# 获取当前目录
|
||||||
|
current_dir = "/root/autodl-tmp/video_llm/Template/牛管家"
|
||||||
|
print(current_dir)
|
||||||
|
# 查找所有片段文件
|
||||||
|
pattern = os.path.join(current_dir, "*.txt")
|
||||||
|
segment_files = glob.glob(pattern)
|
||||||
|
|
||||||
|
# if not segment_files:
|
||||||
|
# print("错误: 未找到任何片段文件")
|
||||||
|
# return
|
||||||
|
|
||||||
|
# print(f"找到 {len(segment_files)} 个片段文件:")
|
||||||
|
# #frame = extract_json_from_file(segment_files[1])
|
||||||
|
# #print(frame["content"][0]["id"])
|
||||||
|
# # print(frame)
|
||||||
|
# # for file_path in segment_files:
|
||||||
|
# # print(f" - {os.path.basename(file_path)}")
|
||||||
|
# for file_path in segment_files:
|
||||||
|
# frame = extract_json_from_file(file_path)
|
||||||
|
# print(frame)
|
||||||
|
|
||||||
|
# if __name__ == "__main__":
|
||||||
|
# main()
|
||||||
|
|
||||||
|
|
||||||
|
# 合并片段
|
||||||
|
print("\n开始合并片段...")
|
||||||
|
merged_data = merge_segments(segment_files)
|
||||||
|
|
||||||
|
if not merged_data["content"]:
|
||||||
|
print("错误: 合并失败,没有有效内容")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 生成输出文件名
|
||||||
|
base_name = "牛管家"
|
||||||
|
output_file = os.path.join(current_dir, f"{base_name}_merged_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json")
|
||||||
|
|
||||||
|
# 保存合并结果
|
||||||
|
if save_merged_result(merged_data, output_file):
|
||||||
|
print(f"\n合并完成!")
|
||||||
|
print(f"总片段数: {len(segment_files)}")
|
||||||
|
print(f"总内容条目: {len(merged_data['content'])}")
|
||||||
|
print(f"输出文件: {os.path.basename(output_file)}")
|
||||||
|
|
||||||
|
# 显示统计信息
|
||||||
|
print(f"\n统计信息:")
|
||||||
|
print(f"- 口播内容长度: {len(merged_data['total_Oral broadcasting'])} 字符")
|
||||||
|
print(f"- 摘要长度: {len(merged_data['summary'])} 字符")
|
||||||
|
print(f"- 内容条目数: {len(merged_data['content'])}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Loading…
x
Reference in New Issue
Block a user