From 184286e5e0dbd968ec4bd2cc1aaa428448367e3f Mon Sep 17 00:00:00 2001 From: linyq Date: Sun, 3 Aug 2025 17:06:55 +0800 Subject: [PATCH] =?UTF-8?q?feat(script):=20=E5=90=88=E5=B9=B6=E8=84=9A?= =?UTF-8?q?=E6=9C=AC=E4=BF=9D=E5=AD=98=E4=B8=8E=E6=A0=BC=E5=BC=8F=E9=AA=8C?= =?UTF-8?q?=E8=AF=81=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重构脚本保存流程,将格式验证整合到保存操作中。新增详细的格式验证错误提示和正确格式示例展示。增强脚本格式检查功能,包括字段类型、格式和必填项验证。 --- app/utils/check_script.py | 85 +++++++++++++++++++---------- webui/components/script_settings.py | 73 ++++++++++++++++--------- 2 files changed, 103 insertions(+), 55 deletions(-) diff --git a/app/utils/check_script.py b/app/utils/check_script.py index 00e6c0f..9c745e6 100644 --- a/app/utils/check_script.py +++ b/app/utils/check_script.py @@ -1,4 +1,5 @@ import json +import re from typing import Dict, Any def check_format(script_content: str) -> Dict[str, Any]: @@ -6,76 +7,104 @@ def check_format(script_content: str) -> Dict[str, Any]: Args: script_content: 脚本内容 Returns: - Dict: {'success': bool, 'message': str} + Dict: {'success': bool, 'message': str, 'details': str} """ try: # 检查是否为有效的JSON data = json.loads(script_content) - + # 检查是否为列表 if not isinstance(data, list): return { '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): + # 检查是否为对象类型 + 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: if field not in clip: return { 'success': False, - 'message': f'第{i+1}个片段缺少必需字段: {field}' + 'message': f'第{i+1}个片段缺少必需字段: {field}', + 'details': f'必需字段: {", ".join(required_fields)}' } - - # 检查字段类型 - if not isinstance(clip['narration'], str): + + # 验证 _id 字段 + if not isinstance(clip['_id'], int) or clip['_id'] <= 0: return { '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 { '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 { 'success': False, - 'message': f'第{i+1}个片段的timestamp必须是字符串' + 'message': f'第{i+1}个片段的picture必须是非空字符串', + 'details': f'当前值: {clip.get("picture", "未定义")}' } - - # 检查字段内容不能为空 - if not clip['narration'].strip(): + + # 验证 narration 字段 + if not isinstance(clip['narration'], str) or not clip['narration'].strip(): return { '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 { 'success': False, - 'message': f'第{i+1}个片段的picture不能为空' - } - if not clip['timestamp'].strip(): - return { - 'success': False, - 'message': f'第{i+1}个片段的timestamp不能为空' + 'message': f'第{i+1}个片段的OST必须是整数', + 'details': f'当前值: {clip["OST"]} (类型: {type(clip["OST"]).__name__}),常用值: 0, 1, 2' } return { 'success': True, - 'message': '脚本格式检查通过' + 'message': '脚本格式检查通过', + 'details': f'共验证 {len(data)} 个脚本片段,格式正确' } except json.JSONDecodeError as e: return { 'success': False, - 'message': f'JSON格式错误: {str(e)}' + 'message': f'JSON格式错误: {str(e)}', + 'details': '请检查JSON语法,确保所有括号、引号、逗号正确' } except Exception as e: return { 'success': False, - 'message': f'检查过程中发生错误: {str(e)}' + 'message': f'检查过程中发生错误: {str(e)}', + 'details': '请联系技术支持' } diff --git a/webui/components/script_settings.py b/webui/components/script_settings.py index 0caa122..42ff794 100644 --- a/webui/components/script_settings.py +++ b/webui/components/script_settings.py @@ -336,30 +336,9 @@ def render_script_buttons(tr, params): height=180 ) - # 操作按钮行 - 移除裁剪视频按钮,使用统一裁剪策略 - button_cols = st.columns(2) # 改为2列布局 - with button_cols[0]: - 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 + # 操作按钮行 - 合并格式检查和保存功能 + if st.button(tr("Save Script"), key="save_script", use_container_width=True): + save_script_with_validation(tr, video_clip_json_details) 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)}") -def save_script(tr, video_clip_json_details): - """保存视频脚本""" +def save_script_with_validation(tr, video_clip_json_details): + """保存视频脚本(包含格式验证)""" if not video_clip_json_details: st.error(tr("请输入视频脚本")) 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")): script_dir = utils.script_dir() 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 # 显示成功消息 - st.success(tr("Script saved successfully")) + st.success("✅ 脚本格式验证通过,保存成功!") # 强制重新加载页面更新选择框 time.sleep(0.5) # 给一点时间让用户看到成功消息