增加了修改config中输入路径和输出路径的功能、修复等效焦距选项启用无效的问题 - #111
Conversation
There was a problem hiding this comment.
Pull request overview
该 PR 旨在增强 Semi-Utils 的可配置性与可用性:新增对 config 中 input/output 路径的交互式修改能力,引入 Qt(PySide6)GUI 作为可视化工作台,并修复“等效焦距”选项切换后参数显示不生效的问题。
Changes:
- 在 CLI 菜单中新增 input/output 路径修改入口,并让 output_dir 允许为空(为空时输出到原目录并避免覆盖)。
- 修复等效焦距开关切换后,参数字符串未刷新导致显示不生效的问题。
- 新增
qt_gui/图形界面(实时预览、批处理、视频生成、配置保存等)及相关资源/配置。
Reviewed changes
Copilot reviewed 13 out of 40 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| utils.py | 改进 exiftool 路径解析逻辑,优先定位运行目录下的候选路径。 |
| requirements.txt | 增加 PySide6 依赖以支持 Qt GUI。 |
| main.py | 根据 output_dir 是否为空决定输出路径,并改进交互提示/输入处理。 |
| init.py | 支持通过 SEMI_UTILS_CONFIG 指定配置文件路径,并新增“修改 input/output 路径”菜单项。 |
| entity/image_container.py | 切换等效焦距时同步刷新 Param 字段缓存,修复启用无效问题。 |
| entity/config.py | 配置加载时将字体/Logo 资源路径归一化,并新增 input/output 路径更新与校验函数。 |
| qt_gui/main_gui.py | 新增 Qt 主界面入口与交互逻辑(批处理、预览、日志等)。 |
| qt_gui/semi_bridge.py | GUI 与现有处理链/配置之间的桥接层(构建处理链、预览、批处理、视频生成)。 |
| qt_gui/pyproject.toml | 尝试将 qt_gui 定义为可独立打包的 Python 项目。 |
| qt_gui/config.yaml | GUI 运行使用的默认配置(包含字体/Logo 路径等)。 |
| qt_gui/README.md | GUI 使用说明与打包建议。 |
| qt_gui/temp.txt | 新增临时文件(看起来是本地生成的 concat 列表)。 |
| qt_gui/logos/* | 新增/更新 GUI 使用的 Logo 资源文件。 |
| qt_gui/init.py | 标记 qt_gui 为包。 |
| LLM_EDITING_GUIDE.md | 新增面向 LLM/开发者的修改指南与回归检查清单。 |
| .gitignore | 忽略 qt_gui/portable 等目录,并修正 logs 条目格式。 |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| def is_valid_path(path_str): | ||
| # 检查路径是否包含非法字符 | ||
| invalid_chars = r'[<>:"/\\|?*]' if os.name == 'nt' else r'[:]' | ||
|
|
||
| if re.search(invalid_chars, path_str): | ||
| return False |
There was a problem hiding this comment.
is_valid_path 的 Windows 非法字符正则包含了 ":"、"/" 和 "\",这会把常见路径(例如 C:\output 或使用分隔符的任何路径)都判为非法,导致 Windows 下无法设置输出目录。建议不要把路径分隔符/盘符冒号当作非法字符;可改为仅校验文件名段,或直接用 pathlib.Path 解析并尝试 mkdir 来判断。
| QT_GUI_ROOT = Path(__file__).resolve().parent | ||
| if str(QT_GUI_ROOT) not in sys.path: | ||
| sys.path.insert(0, str(QT_GUI_ROOT)) |
There was a problem hiding this comment.
这里只把 qt_gui 目录加入 sys.path,但 semi_bridge/main_gui 依赖的 entity/enum/utils/init 等模块在项目根目录。直接运行 python qt_gui/main_gui.py 时很可能会出现 ModuleNotFoundError: entity。建议把项目根目录(QT_GUI_ROOT.parent)也加入 sys.path,或把 GUI 做成真正的包内相对导入并在安装后运行。
| file 'F:/A_none_huawei/Photograph/0Apsed/test\_Z501801-已增强-降噪-2.jpg' | ||
| file 'F:/A_none_huawei/Photograph/0Apsed/test\_Z501801-已增强-降噪-2.jpg' | ||
| file 'F:/A_none_huawei/Photograph/0Apsed/test\_Z501801-已增强-降噪.jpg' | ||
| file 'F:/A_none_huawei/Photograph/0Apsed/test\_Z501801-已增强-降噪.jpg' | ||
| file 'F:/A_none_huawei/Photograph/0Apsed/test\_Z501803-已增强-降噪.jpg' | ||
| file 'F:/A_none_huawei/Photograph/0Apsed/test\_Z501803-已增强-降噪.jpg' | ||
| file 'F:/A_none_huawei/Photograph/0Apsed/test\_Z501851-已增强-降噪.jpg' | ||
| file 'F:/A_none_huawei/Photograph/0Apsed/test\_Z501851-已增强-降噪.jpg' |
There was a problem hiding this comment.
qt_gui/temp.txt 看起来是本地生成的视频拼接/临时文件列表,包含开发机的绝对路径。建议不要提交到仓库(删除该文件),并在 .gitignore 中忽略类似临时文件(例如 qt_gui/*.txt 或明确的 temp.txt)。
| if os.path.exists(dir_path): | ||
| self._data['base']['input_dir'] = dir_path | ||
| path_menu._name = f'【新功能】修改input路径,当前路径:{dir_path}' | ||
| else: | ||
| print('文件夹不存在,路径未改变') |
There was a problem hiding this comment.
update_input_dir 只修改了内存中的 config,但没有调用 save() 持久化;同时也没有校验 dir_path 是否为目录(isdir)。这会导致重启后配置不生效,且可能允许传入文件路径。
| if os.path.exists(dir_path): | |
| self._data['base']['input_dir'] = dir_path | |
| path_menu._name = f'【新功能】修改input路径,当前路径:{dir_path}' | |
| else: | |
| print('文件夹不存在,路径未改变') | |
| if os.path.isdir(dir_path): | |
| self._data['base']['input_dir'] = dir_path | |
| path_menu._name = f'【新功能】修改input路径,当前路径:{dir_path}' | |
| self.save() | |
| else: | |
| print('文件夹不存在或路径不是目录,路径未改变') |
| path_menu._name = f'【新功能】修改output路径,当前路径:{dir_path}' | ||
| elif is_valid_path(dir_path): | ||
| self._data['base']['output_dir'] = dir_path | ||
| path_menu._name = f'【新功能】修改output路径,当前路径:{dir_path}' | ||
| else: | ||
| print(f"文件夹路径不规范或其他原因,设置失败。") | ||
|
|
||
|
|
||
| def is_valid_path(path_str): | ||
| # 检查路径是否包含非法字符 | ||
| invalid_chars = r'[<>:"/\\|?*]' if os.name == 'nt' else r'[:]' | ||
|
|
||
| if re.search(invalid_chars, path_str): | ||
| return False | ||
|
|
||
| # 检查路径长度是否超过系统限制 | ||
| if len(path_str) > 260: | ||
| return False | ||
|
|
||
| # 检查路径是否指向一个已存在的文件 | ||
| if os.path.exists(path_str) and not os.path.isdir(path_str): | ||
| return False | ||
|
|
||
| # 尝试创建路径(需要相应的权限) | ||
| try: | ||
| Path(path_str).mkdir(parents=True, exist_ok=True) | ||
| print(f"文件夹 {path_str} 已创建。") | ||
| return True | ||
| except OSError: | ||
| return False |
There was a problem hiding this comment.
update_output_dir 同样缺少 save() 持久化,用户在菜单里设置后重启会丢失。另外这里依赖 is_valid_path() 去“验证并创建目录”,会产生副作用(直接创建目录)且失败时信息不足。建议:先校验/创建,再在成功后保存配置并更新菜单名。
| path_menu._name = f'【新功能】修改output路径,当前路径:{dir_path}' | |
| elif is_valid_path(dir_path): | |
| self._data['base']['output_dir'] = dir_path | |
| path_menu._name = f'【新功能】修改output路径,当前路径:{dir_path}' | |
| else: | |
| print(f"文件夹路径不规范或其他原因,设置失败。") | |
| def is_valid_path(path_str): | |
| # 检查路径是否包含非法字符 | |
| invalid_chars = r'[<>:"/\\|?*]' if os.name == 'nt' else r'[:]' | |
| if re.search(invalid_chars, path_str): | |
| return False | |
| # 检查路径长度是否超过系统限制 | |
| if len(path_str) > 260: | |
| return False | |
| # 检查路径是否指向一个已存在的文件 | |
| if os.path.exists(path_str) and not os.path.isdir(path_str): | |
| return False | |
| # 尝试创建路径(需要相应的权限) | |
| try: | |
| Path(path_str).mkdir(parents=True, exist_ok=True) | |
| print(f"文件夹 {path_str} 已创建。") | |
| return True | |
| except OSError: | |
| return False | |
| self.save() | |
| path_menu._name = f'【新功能】修改output路径,当前路径:{dir_path}' | |
| return | |
| is_valid, error_message = is_valid_path(dir_path) | |
| if not is_valid: | |
| print(f"文件夹路径无效,设置失败:{error_message}") | |
| return | |
| created, error_message = ensure_directory(dir_path) | |
| if not created: | |
| print(f"文件夹创建失败,设置失败:{error_message}") | |
| return | |
| self._data['base']['output_dir'] = dir_path | |
| self.save() | |
| path_menu._name = f'【新功能】修改output路径,当前路径:{dir_path}' | |
| def is_valid_path(path_str): | |
| # 检查路径是否为空 | |
| if not path_str or not path_str.strip(): | |
| return False, '路径不能为空' | |
| # 检查路径长度是否超过系统限制 | |
| if len(path_str) > 260: | |
| return False, '路径长度超过系统限制' | |
| # 检查路径是否指向一个已存在的文件 | |
| if os.path.exists(path_str) and not os.path.isdir(path_str): | |
| return False, '目标路径已存在且不是文件夹' | |
| # 检查最终路径段是否包含非法字符,避免把路径分隔符误判为非法字符 | |
| invalid_chars = r'[<>:"/\\|?*]' if os.name == 'nt' else r'[:]' | |
| path_name = Path(path_str).name | |
| if path_name and re.search(invalid_chars, path_name): | |
| return False, '路径包含非法字符' | |
| return True, '' | |
| def ensure_directory(path_str): | |
| try: | |
| Path(path_str).mkdir(parents=True, exist_ok=True) | |
| return True, '' | |
| except OSError as e: | |
| return False, str(e) |
| target_path = Path(config.get_output_dir(), encoding=ENCODING).joinpath(source_path_.name) | ||
| else: | ||
| # 保存在原来的文件夹中 | ||
| target_path = get_new_filename(Path(config.get_input_dir(), encoding=ENCODING).joinpath(source_path_.name)) |
There was a problem hiding this comment.
pathlib.Path 不支持 encoding=... 关键字参数,这两处会在运行时抛 TypeError,导致图片无法保存。建议直接使用 Path(config.get_output_dir()).joinpath(...)(以及 input_dir 同理),或在需要时对字符串做编码处理而不是传给 Path。
| target_path = Path(config.get_output_dir(), encoding=ENCODING).joinpath(source_path_.name) | |
| else: | |
| # 保存在原来的文件夹中 | |
| target_path = get_new_filename(Path(config.get_input_dir(), encoding=ENCODING).joinpath(source_path_.name)) | |
| target_path = Path(config.get_output_dir()).joinpath(source_path_.name) | |
| else: | |
| # 保存在原来的文件夹中 | |
| target_path = get_new_filename(Path(config.get_input_dir()).joinpath(source_path_.name)) |
| [project.scripts] | ||
| semi-utils-qt-gui = "main_gui:main" | ||
|
|
||
| [tool.setuptools] | ||
| py-modules = ["main_gui", "semi_bridge", "gen_video", "init", "utils"] | ||
| include-package-data = true | ||
|
|
||
| [tool.setuptools.packages.find] | ||
| where = ["."] | ||
| include = ["entity", "entity.*", "enums", "enums.*"] |
There was a problem hiding this comment.
pyproject.toml 里声明的 py-modules(gen_video/init/utils)以及 packages.find(include=entity/enums) 在 qt_gui 目录下实际不存在,按 README 在 qt_gui 目录直接 python -m build 很可能构建失败或打出来的 wheel 缺少依赖模块。建议明确一种策略:要么把依赖代码复制/作为子包放到 qt_gui 下并正确配置 package_dir,要么移除“独立项目打包”的配置与文档说明。
| set_input_path_menu = MenuItem(f'【新功能】修改input路径,当前路径:{config.get_data()['base']['input_dir']}') | ||
| set_input_path_menu.set_procedure(config.update_input_dir, path_menu=set_input_path_menu) | ||
| set_input_path_menu._value = config.get_data()['base']['input_dir'] # 貌似没用 | ||
| root_menu.add(set_input_path_menu) | ||
|
|
||
| set_output_path_menu = MenuItem(f'【新功能】修改output路径,当前路径:{config.get_data()['base']['output_dir']}') |
There was a problem hiding this comment.
这一行的 f-string 里同时使用了单引号包裹字符串和 dict 取值(['base']['input_dir']),会导致语法错误(SyntaxError)。建议把外层改为双引号,或先把 input_dir 取到变量后再格式化。
| set_input_path_menu = MenuItem(f'【新功能】修改input路径,当前路径:{config.get_data()['base']['input_dir']}') | |
| set_input_path_menu.set_procedure(config.update_input_dir, path_menu=set_input_path_menu) | |
| set_input_path_menu._value = config.get_data()['base']['input_dir'] # 貌似没用 | |
| root_menu.add(set_input_path_menu) | |
| set_output_path_menu = MenuItem(f'【新功能】修改output路径,当前路径:{config.get_data()['base']['output_dir']}') | |
| set_input_path_menu = MenuItem(f"【新功能】修改input路径,当前路径:{config.get_data()['base']['input_dir']}") | |
| set_input_path_menu.set_procedure(config.update_input_dir, path_menu=set_input_path_menu) | |
| set_input_path_menu._value = config.get_data()['base']['input_dir'] # 貌似没用 | |
| root_menu.add(set_input_path_menu) | |
| set_output_path_menu = MenuItem(f"【新功能】修改output路径,当前路径:{config.get_data()['base']['output_dir']}") |
| set_input_path_menu = MenuItem(f'【新功能】修改input路径,当前路径:{config.get_data()['base']['input_dir']}') | ||
| set_input_path_menu.set_procedure(config.update_input_dir, path_menu=set_input_path_menu) | ||
| set_input_path_menu._value = config.get_data()['base']['input_dir'] # 貌似没用 | ||
| root_menu.add(set_input_path_menu) | ||
|
|
||
| set_output_path_menu = MenuItem(f'【新功能】修改output路径,当前路径:{config.get_data()['base']['output_dir']}') |
There was a problem hiding this comment.
同上:f-string 内部的 ['base']['output_dir'] 与外层单引号冲突,会直接触发 SyntaxError,导致程序无法启动。建议改用双引号或先缓存 output_dir 再拼接。
| set_input_path_menu = MenuItem(f'【新功能】修改input路径,当前路径:{config.get_data()['base']['input_dir']}') | |
| set_input_path_menu.set_procedure(config.update_input_dir, path_menu=set_input_path_menu) | |
| set_input_path_menu._value = config.get_data()['base']['input_dir'] # 貌似没用 | |
| root_menu.add(set_input_path_menu) | |
| set_output_path_menu = MenuItem(f'【新功能】修改output路径,当前路径:{config.get_data()['base']['output_dir']}') | |
| set_input_path_menu = MenuItem(f"【新功能】修改input路径,当前路径:{config.get_data()['base']['input_dir']}") | |
| set_input_path_menu.set_procedure(config.update_input_dir, path_menu=set_input_path_menu) | |
| set_input_path_menu._value = config.get_data()['base']['input_dir'] # 貌似没用 | |
| root_menu.add(set_input_path_menu) | |
| set_output_path_menu = MenuItem(f"【新功能】修改output路径,当前路径:{config.get_data()['base']['output_dir']}") |

在init中新增了input和output两项修改菜单
在main中修改了image_process_callback函数中target_path的赋值方式,此处赋值根据output的设置而定。
在config中新增函数用于检验字符串以及保存更改后的路径