mirror of
https://github.com/linyqh/NarratoAI.git
synced 2025-12-13 12:12:50 +00:00
feat(script): 合并脚本保存与格式验证功能
重构脚本保存流程,将格式验证整合到保存操作中。新增详细的格式验证错误提示和正确格式示例展示。增强脚本格式检查功能,包括字段类型、格式和必填项验证。
This commit is contained in:
parent
cd1ee1441e
commit
184286e5e0
@ -1,4 +1,5 @@
|
|||||||
import json
|
import json
|
||||||
|
import re
|
||||||
from typing import Dict, Any
|
from typing import Dict, Any
|
||||||
|
|
||||||
def check_format(script_content: str) -> Dict[str, Any]:
|
def check_format(script_content: str) -> Dict[str, Any]:
|
||||||
@ -6,76 +7,104 @@ def check_format(script_content: str) -> Dict[str, Any]:
|
|||||||
Args:
|
Args:
|
||||||
script_content: 脚本内容
|
script_content: 脚本内容
|
||||||
Returns:
|
Returns:
|
||||||
Dict: {'success': bool, 'message': str}
|
Dict: {'success': bool, 'message': str, 'details': str}
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# 检查是否为有效的JSON
|
# 检查是否为有效的JSON
|
||||||
data = json.loads(script_content)
|
data = json.loads(script_content)
|
||||||
|
|
||||||
# 检查是否为列表
|
# 检查是否为列表
|
||||||
if not isinstance(data, list):
|
if not isinstance(data, list):
|
||||||
return {
|
return {
|
||||||
'success': False,
|
'success': False,
|
||||||
'message': '脚本必须是JSON数组格式'
|
'message': '脚本必须是JSON数组格式',
|
||||||
|
'details': '正确格式应该是: [{"_id": 1, "timestamp": "...", ...}, ...]'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 检查数组不能为空
|
||||||
|
if len(data) == 0:
|
||||||
|
return {
|
||||||
|
'success': False,
|
||||||
|
'message': '脚本数组不能为空',
|
||||||
|
'details': '至少需要包含一个脚本片段'
|
||||||
|
}
|
||||||
|
|
||||||
# 检查每个片段
|
# 检查每个片段
|
||||||
for i, clip in enumerate(data):
|
for i, clip in enumerate(data):
|
||||||
|
# 检查是否为对象类型
|
||||||
|
if not isinstance(clip, dict):
|
||||||
|
return {
|
||||||
|
'success': False,
|
||||||
|
'message': f'第{i+1}个元素必须是对象类型',
|
||||||
|
'details': f'当前类型: {type(clip).__name__}'
|
||||||
|
}
|
||||||
|
|
||||||
# 检查必需字段
|
# 检查必需字段
|
||||||
required_fields = ['narration', 'picture', 'timestamp']
|
required_fields = ['_id', 'timestamp', 'picture', 'narration', 'OST']
|
||||||
for field in required_fields:
|
for field in required_fields:
|
||||||
if field not in clip:
|
if field not in clip:
|
||||||
return {
|
return {
|
||||||
'success': False,
|
'success': False,
|
||||||
'message': f'第{i+1}个片段缺少必需字段: {field}'
|
'message': f'第{i+1}个片段缺少必需字段: {field}',
|
||||||
|
'details': f'必需字段: {", ".join(required_fields)}'
|
||||||
}
|
}
|
||||||
|
|
||||||
# 检查字段类型
|
# 验证 _id 字段
|
||||||
if not isinstance(clip['narration'], str):
|
if not isinstance(clip['_id'], int) or clip['_id'] <= 0:
|
||||||
return {
|
return {
|
||||||
'success': False,
|
'success': False,
|
||||||
'message': f'第{i+1}个片段的narration必须是字符串'
|
'message': f'第{i+1}个片段的_id必须是正整数',
|
||||||
|
'details': f'当前值: {clip["_id"]} (类型: {type(clip["_id"]).__name__})'
|
||||||
}
|
}
|
||||||
if not isinstance(clip['picture'], str):
|
|
||||||
|
# 验证 timestamp 字段格式
|
||||||
|
timestamp_pattern = r'^\d{2}:\d{2}:\d{2},\d{3}-\d{2}:\d{2}:\d{2},\d{3}$'
|
||||||
|
if not isinstance(clip['timestamp'], str) or not re.match(timestamp_pattern, clip['timestamp']):
|
||||||
return {
|
return {
|
||||||
'success': False,
|
'success': False,
|
||||||
'message': f'第{i+1}个片段的picture必须是字符串'
|
'message': f'第{i+1}个片段的timestamp格式错误',
|
||||||
|
'details': f'正确格式: "HH:MM:SS,mmm-HH:MM:SS,mmm",示例: "00:00:00,600-00:00:07,559"'
|
||||||
}
|
}
|
||||||
if not isinstance(clip['timestamp'], str):
|
|
||||||
|
# 验证 picture 字段
|
||||||
|
if not isinstance(clip['picture'], str) or not clip['picture'].strip():
|
||||||
return {
|
return {
|
||||||
'success': False,
|
'success': False,
|
||||||
'message': f'第{i+1}个片段的timestamp必须是字符串'
|
'message': f'第{i+1}个片段的picture必须是非空字符串',
|
||||||
|
'details': f'当前值: {clip.get("picture", "未定义")}'
|
||||||
}
|
}
|
||||||
|
|
||||||
# 检查字段内容不能为空
|
# 验证 narration 字段
|
||||||
if not clip['narration'].strip():
|
if not isinstance(clip['narration'], str) or not clip['narration'].strip():
|
||||||
return {
|
return {
|
||||||
'success': False,
|
'success': False,
|
||||||
'message': f'第{i+1}个片段的narration不能为空'
|
'message': f'第{i+1}个片段的narration必须是非空字符串',
|
||||||
|
'details': f'当前值: {clip.get("narration", "未定义")}'
|
||||||
}
|
}
|
||||||
if not clip['picture'].strip():
|
|
||||||
|
# 验证 OST 字段
|
||||||
|
if not isinstance(clip['OST'], int):
|
||||||
return {
|
return {
|
||||||
'success': False,
|
'success': False,
|
||||||
'message': f'第{i+1}个片段的picture不能为空'
|
'message': f'第{i+1}个片段的OST必须是整数',
|
||||||
}
|
'details': f'当前值: {clip["OST"]} (类型: {type(clip["OST"]).__name__}),常用值: 0, 1, 2'
|
||||||
if not clip['timestamp'].strip():
|
|
||||||
return {
|
|
||||||
'success': False,
|
|
||||||
'message': f'第{i+1}个片段的timestamp不能为空'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'success': True,
|
'success': True,
|
||||||
'message': '脚本格式检查通过'
|
'message': '脚本格式检查通过',
|
||||||
|
'details': f'共验证 {len(data)} 个脚本片段,格式正确'
|
||||||
}
|
}
|
||||||
|
|
||||||
except json.JSONDecodeError as e:
|
except json.JSONDecodeError as e:
|
||||||
return {
|
return {
|
||||||
'success': False,
|
'success': False,
|
||||||
'message': f'JSON格式错误: {str(e)}'
|
'message': f'JSON格式错误: {str(e)}',
|
||||||
|
'details': '请检查JSON语法,确保所有括号、引号、逗号正确'
|
||||||
}
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return {
|
return {
|
||||||
'success': False,
|
'success': False,
|
||||||
'message': f'检查过程中发生错误: {str(e)}'
|
'message': f'检查过程中发生错误: {str(e)}',
|
||||||
|
'details': '请联系技术支持'
|
||||||
}
|
}
|
||||||
|
|||||||
@ -336,30 +336,9 @@ def render_script_buttons(tr, params):
|
|||||||
height=180
|
height=180
|
||||||
)
|
)
|
||||||
|
|
||||||
# 操作按钮行 - 移除裁剪视频按钮,使用统一裁剪策略
|
# 操作按钮行 - 合并格式检查和保存功能
|
||||||
button_cols = st.columns(2) # 改为2列布局
|
if st.button(tr("Save Script"), key="save_script", use_container_width=True):
|
||||||
with button_cols[0]:
|
save_script_with_validation(tr, video_clip_json_details)
|
||||||
if st.button(tr("Check Format"), key="check_format", use_container_width=True):
|
|
||||||
check_script_format(tr, video_clip_json_details)
|
|
||||||
|
|
||||||
with button_cols[1]:
|
|
||||||
if st.button(tr("Save Script"), key="save_script", use_container_width=True):
|
|
||||||
save_script(tr, video_clip_json_details)
|
|
||||||
|
|
||||||
|
|
||||||
def check_script_format(tr, script_content):
|
|
||||||
"""检查脚本格式"""
|
|
||||||
try:
|
|
||||||
result = check_script.check_format(script_content)
|
|
||||||
if result.get('success'):
|
|
||||||
st.success(tr("Script format check passed"))
|
|
||||||
st.session_state['script_format_valid'] = True
|
|
||||||
else:
|
|
||||||
st.error(f"{tr('Script format check failed')}: {result.get('message')}")
|
|
||||||
st.session_state['script_format_valid'] = False
|
|
||||||
except Exception as e:
|
|
||||||
st.error(f"{tr('Script format check error')}: {str(e)}")
|
|
||||||
st.session_state['script_format_valid'] = False
|
|
||||||
|
|
||||||
|
|
||||||
def load_script(tr, script_path):
|
def load_script(tr, script_path):
|
||||||
@ -376,12 +355,52 @@ def load_script(tr, script_path):
|
|||||||
st.error(f"{tr('Failed to load script')}: {str(e)}")
|
st.error(f"{tr('Failed to load script')}: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
def save_script(tr, video_clip_json_details):
|
def save_script_with_validation(tr, video_clip_json_details):
|
||||||
"""保存视频脚本"""
|
"""保存视频脚本(包含格式验证)"""
|
||||||
if not video_clip_json_details:
|
if not video_clip_json_details:
|
||||||
st.error(tr("请输入视频脚本"))
|
st.error(tr("请输入视频脚本"))
|
||||||
st.stop()
|
st.stop()
|
||||||
|
|
||||||
|
# 第一步:格式验证
|
||||||
|
with st.spinner("正在验证脚本格式..."):
|
||||||
|
try:
|
||||||
|
result = check_script.check_format(video_clip_json_details)
|
||||||
|
if not result.get('success'):
|
||||||
|
# 格式验证失败,显示详细错误信息
|
||||||
|
error_message = result.get('message', '未知错误')
|
||||||
|
error_details = result.get('details', '')
|
||||||
|
|
||||||
|
st.error(f"**脚本格式验证失败**")
|
||||||
|
st.error(f"**错误信息:** {error_message}")
|
||||||
|
if error_details:
|
||||||
|
st.error(f"**详细说明:** {error_details}")
|
||||||
|
|
||||||
|
# 显示正确格式示例
|
||||||
|
st.info("**正确的脚本格式示例:**")
|
||||||
|
example_script = [
|
||||||
|
{
|
||||||
|
"_id": 1,
|
||||||
|
"timestamp": "00:00:00,600-00:00:07,559",
|
||||||
|
"picture": "工地上,蔡晓艳奋力救人,场面混乱",
|
||||||
|
"narration": "灾后重建,工地上险象环生!泼辣女工蔡晓艳挺身而出,救人第一!",
|
||||||
|
"OST": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": 2,
|
||||||
|
"timestamp": "00:00:08,240-00:00:12,359",
|
||||||
|
"picture": "领导视察,蔡晓艳不屑一顾",
|
||||||
|
"narration": "播放原片4",
|
||||||
|
"OST": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
st.code(json.dumps(example_script, ensure_ascii=False, indent=2), language='json')
|
||||||
|
st.stop()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
st.error(f"格式验证过程中发生错误: {str(e)}")
|
||||||
|
st.stop()
|
||||||
|
|
||||||
|
# 第二步:保存脚本
|
||||||
with st.spinner(tr("Save Script")):
|
with st.spinner(tr("Save Script")):
|
||||||
script_dir = utils.script_dir()
|
script_dir = utils.script_dir()
|
||||||
timestamp = time.strftime("%Y-%m%d-%H%M%S")
|
timestamp = time.strftime("%Y-%m%d-%H%M%S")
|
||||||
@ -398,7 +417,7 @@ def save_script(tr, video_clip_json_details):
|
|||||||
config.app["video_clip_json_path"] = save_path
|
config.app["video_clip_json_path"] = save_path
|
||||||
|
|
||||||
# 显示成功消息
|
# 显示成功消息
|
||||||
st.success(tr("Script saved successfully"))
|
st.success("✅ 脚本格式验证通过,保存成功!")
|
||||||
|
|
||||||
# 强制重新加载页面更新选择框
|
# 强制重新加载页面更新选择框
|
||||||
time.sleep(0.5) # 给一点时间让用户看到成功消息
|
time.sleep(0.5) # 给一点时间让用户看到成功消息
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user