fix
This commit is contained in:
256
docs/COPY_FEATURE_DEMO.md
Normal file
256
docs/COPY_FEATURE_DEMO.md
Normal file
@@ -0,0 +1,256 @@
|
||||
# 模板导入日志复制功能演示
|
||||
|
||||
## 功能演示
|
||||
|
||||
### 1. 基础界面
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 模板导入进度 │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 进度条: ████████████████████████████████████████ 100% │
|
||||
│ 状态: 导入完成 │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 导入结果: ✅ 成功导入 8 个模板 │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 导入日志 [复制日志] [▼] │
|
||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||
│ │ [14:30:20] 开始扫描模板目录... │ │
|
||||
│ │ [14:30:21] 发现 10 个模板文件 │ │
|
||||
│ │ [14:30:22] 开始导入模板: template1 │ │
|
||||
│ │ [14:30:23] ✅ 成功导入: template1 │ │
|
||||
│ │ [14:30:24] 开始导入模板: template2 │ │
|
||||
│ │ [14:30:25] ✅ 成功导入: template2 │ │
|
||||
│ │ ... │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
│ [关闭] │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 2. 复制按钮状态
|
||||
|
||||
#### 默认状态
|
||||
```
|
||||
[📋 复制日志] [▼]
|
||||
```
|
||||
|
||||
#### 复制成功状态(2秒后自动恢复)
|
||||
```
|
||||
[✅ 已复制] [▼]
|
||||
```
|
||||
|
||||
### 3. 下拉菜单展开
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 导入日志 [复制日志] [▲] │
|
||||
│ ┌─────────────────┐ │
|
||||
│ │ 复制详细日志 │ │
|
||||
│ │ 仅复制日志内容 │ │
|
||||
│ └─────────────────┘ │
|
||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||
│ │ 日志内容... │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 复制内容示例
|
||||
|
||||
### 详细格式复制内容
|
||||
|
||||
```
|
||||
=== 模板导入日志 ===
|
||||
导出时间: 2024-01-15 14:30:25
|
||||
当前进度: 导入模板 - 导入完成 (100.0%)
|
||||
导入结果: 成功 - 导入完成 (成功导入 8 个模板)
|
||||
=== 详细日志 ===
|
||||
|
||||
[2024-01-15 14:30:20] 开始扫描模板目录...
|
||||
[2024-01-15 14:30:21] 发现 10 个模板文件
|
||||
[2024-01-15 14:30:22] 开始导入模板: template1
|
||||
[2024-01-15 14:30:23] ✅ 成功导入: template1
|
||||
[2024-01-15 14:30:24] 开始导入模板: template2
|
||||
[2024-01-15 14:30:25] ✅ 成功导入: template2
|
||||
[2024-01-15 14:30:26] 开始导入模板: template3
|
||||
[2024-01-15 14:30:27] ✅ 成功导入: template3
|
||||
[2024-01-15 14:30:28] 开始导入模板: template4
|
||||
[2024-01-15 14:30:29] ✅ 成功导入: template4
|
||||
[2024-01-15 14:30:30] 开始导入模板: template5
|
||||
[2024-01-15 14:30:31] ✅ 成功导入: template5
|
||||
[2024-01-15 14:30:32] 开始导入模板: template6
|
||||
[2024-01-15 14:30:33] ✅ 成功导入: template6
|
||||
[2024-01-15 14:30:34] 开始导入模板: template7
|
||||
[2024-01-15 14:30:35] ✅ 成功导入: template7
|
||||
[2024-01-15 14:30:36] 开始导入模板: template8
|
||||
[2024-01-15 14:30:37] ✅ 成功导入: template8
|
||||
[2024-01-15 14:30:38] 跳过重复模板: template9 (已存在)
|
||||
[2024-01-15 14:30:39] ❌ 导入失败: template10 - 文件格式错误
|
||||
[2024-01-15 14:30:40] 导入完成,成功: 8,失败: 1,跳过: 1
|
||||
```
|
||||
|
||||
### 简单格式复制内容
|
||||
|
||||
```
|
||||
[2024-01-15 14:30:20] 开始扫描模板目录...
|
||||
[2024-01-15 14:30:21] 发现 10 个模板文件
|
||||
[2024-01-15 14:30:22] 开始导入模板: template1
|
||||
[2024-01-15 14:30:23] ✅ 成功导入: template1
|
||||
[2024-01-15 14:30:24] 开始导入模板: template2
|
||||
[2024-01-15 14:30:25] ✅ 成功导入: template2
|
||||
[2024-01-15 14:30:26] 开始导入模板: template3
|
||||
[2024-01-15 14:30:27] ✅ 成功导入: template3
|
||||
[2024-01-15 14:30:28] 开始导入模板: template4
|
||||
[2024-01-15 14:30:29] ✅ 成功导入: template4
|
||||
[2024-01-15 14:30:30] 开始导入模板: template5
|
||||
[2024-01-15 14:30:31] ✅ 成功导入: template5
|
||||
[2024-01-15 14:30:32] 开始导入模板: template6
|
||||
[2024-01-15 14:30:33] ✅ 成功导入: template6
|
||||
[2024-01-15 14:30:34] 开始导入模板: template7
|
||||
[2024-01-15 14:30:35] ✅ 成功导入: template7
|
||||
[2024-01-15 14:30:36] 开始导入模板: template8
|
||||
[2024-01-15 14:30:37] ✅ 成功导入: template8
|
||||
[2024-01-15 14:30:38] 跳过重复模板: template9 (已存在)
|
||||
[2024-01-15 14:30:39] ❌ 导入失败: template10 - 文件格式错误
|
||||
[2024-01-15 14:30:40] 导入完成,成功: 8,失败: 1,跳过: 1
|
||||
```
|
||||
|
||||
## 用户操作流程
|
||||
|
||||
### 场景1:快速复制详细日志
|
||||
|
||||
1. 用户点击模板导入
|
||||
2. 导入进度弹窗显示
|
||||
3. 导入完成后,用户直接点击 **[复制日志]** 按钮
|
||||
4. 系统复制详细格式日志到剪贴板
|
||||
5. 按钮显示 **[✅ 已复制]** 状态
|
||||
6. 2秒后自动恢复为 **[📋 复制日志]**
|
||||
|
||||
### 场景2:选择复制格式
|
||||
|
||||
1. 用户点击模板导入
|
||||
2. 导入进度弹窗显示
|
||||
3. 用户点击下拉箭头 **[▼]** 展开菜单
|
||||
4. 用户选择 **"仅复制日志内容"**
|
||||
5. 系统复制简单格式日志到剪贴板
|
||||
6. 下拉菜单自动关闭
|
||||
7. 主按钮显示 **[✅ 已复制]** 状态
|
||||
|
||||
### 场景3:导入失败时复制错误信息
|
||||
|
||||
```
|
||||
=== 模板导入日志 ===
|
||||
导出时间: 2024-01-15 14:35:12
|
||||
当前进度: 导入模板 - 导入失败 (60.0%)
|
||||
导入结果: 失败 - 部分模板导入失败 (成功导入 3 个模板) (失败 2 个模板)
|
||||
=== 详细日志 ===
|
||||
|
||||
[2024-01-15 14:35:05] 开始扫描模板目录...
|
||||
[2024-01-15 14:35:06] 发现 5 个模板文件
|
||||
[2024-01-15 14:35:07] 开始导入模板: template1
|
||||
[2024-01-15 14:35:08] ✅ 成功导入: template1
|
||||
[2024-01-15 14:35:09] 开始导入模板: template2
|
||||
[2024-01-15 14:35:10] ❌ 导入失败: template2 - 文件损坏,无法读取
|
||||
[2024-01-15 14:35:11] 开始导入模板: template3
|
||||
[2024-01-15 14:35:12] ❌ 导入失败: template3 - 缺少必要的配置文件
|
||||
[2024-01-15 14:35:13] 导入中断,请检查错误信息
|
||||
```
|
||||
|
||||
## 技术细节
|
||||
|
||||
### 按钮样式
|
||||
|
||||
```css
|
||||
/* 主复制按钮 */
|
||||
.copy-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 4px 8px;
|
||||
font-size: 12px;
|
||||
background-color: #e5e7eb;
|
||||
color: #374151;
|
||||
border-radius: 6px 0 0 6px;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.copy-button:hover {
|
||||
background-color: #d1d5db;
|
||||
}
|
||||
|
||||
/* 下拉按钮 */
|
||||
.dropdown-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 4px;
|
||||
font-size: 12px;
|
||||
background-color: #e5e7eb;
|
||||
color: #374151;
|
||||
border-radius: 0 6px 6px 0;
|
||||
border-left: 1px solid #d1d5db;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
/* 下拉菜单 */
|
||||
.dropdown-menu {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 100%;
|
||||
margin-top: 4px;
|
||||
background-color: white;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
||||
z-index: 10;
|
||||
min-width: 128px;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
padding: 8px 12px;
|
||||
font-size: 12px;
|
||||
color: #374151;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.dropdown-item:hover {
|
||||
background-color: #f9fafb;
|
||||
}
|
||||
```
|
||||
|
||||
### 响应式设计
|
||||
|
||||
- **桌面端**: 完整的按钮组和下拉菜单
|
||||
- **平板端**: 保持完整功能,调整按钮大小
|
||||
- **移动端**: 简化为单个复制按钮,长按显示选项
|
||||
|
||||
### 无障碍支持
|
||||
|
||||
- **键盘导航**: 支持 Tab 键切换和 Enter 键激活
|
||||
- **屏幕阅读器**: 提供完整的 aria-label 和 title 属性
|
||||
- **高对比度**: 确保在高对比度模式下可见性
|
||||
|
||||
## 测试场景
|
||||
|
||||
### 功能测试
|
||||
- ✅ 有日志时显示复制按钮
|
||||
- ✅ 无日志时隐藏复制按钮
|
||||
- ✅ 详细格式复制正确
|
||||
- ✅ 简单格式复制正确
|
||||
- ✅ 复制状态反馈正确
|
||||
- ✅ 下拉菜单交互正常
|
||||
|
||||
### 兼容性测试
|
||||
- ✅ Chrome 90+
|
||||
- ✅ Firefox 88+
|
||||
- ✅ Safari 14+
|
||||
- ✅ Edge 90+
|
||||
|
||||
### 错误处理测试
|
||||
- ✅ 剪贴板权限被拒绝
|
||||
- ✅ 剪贴板API不可用
|
||||
- ✅ 网络错误情况
|
||||
|
||||
---
|
||||
|
||||
通过这个复制功能,用户可以轻松地复制和分享模板导入日志,大大提升了调试和问题解决的效率。
|
||||
280
docs/PYTHON_CORE_DISTRIBUTION.md
Normal file
280
docs/PYTHON_CORE_DISTRIBUTION.md
Normal file
@@ -0,0 +1,280 @@
|
||||
# Python Core 分发解决方案
|
||||
|
||||
## 问题背景
|
||||
|
||||
当前项目使用 `python -m python_core.services.xxx` 的方式调用Python模块,这在开发环境下工作正常,但打包成exe后会遇到以下问题:
|
||||
|
||||
1. **Python环境依赖**:用户机器可能没有安装Python或缺少依赖包
|
||||
2. **模块路径问题**:python_core目录不在用户的Python路径中
|
||||
3. **分发复杂性**:需要用户单独安装Python环境
|
||||
|
||||
## 解决方案:Tauri Sidecar + PyInstaller
|
||||
|
||||
### 方案概述
|
||||
|
||||
使用PyInstaller将Python代码打包成独立可执行文件,作为Tauri的sidecar与主应用一起分发。
|
||||
|
||||
### 优势
|
||||
|
||||
1. **无需Python环境**:用户无需安装Python
|
||||
2. **依赖自包含**:所有Python依赖都打包在内
|
||||
3. **分发简单**:单个exe文件包含所有功能
|
||||
4. **性能优化**:避免Python解释器启动开销
|
||||
5. **安全性**:代码被编译,不易被逆向
|
||||
|
||||
## 实现步骤
|
||||
|
||||
### 1. 创建Python入口文件
|
||||
|
||||
**文件**: `python_core/main.py`
|
||||
|
||||
这是PyInstaller的入口点,提供统一的命令行接口:
|
||||
|
||||
```python
|
||||
# 支持模块映射
|
||||
MODULE_MAP = {
|
||||
'template_manager': 'services.template_manager',
|
||||
'project_manager': 'services.project_manager',
|
||||
# ... 更多模块
|
||||
}
|
||||
|
||||
# 统一的执行接口
|
||||
def execute_action(module_name, action, params=None, progress_callback=None):
|
||||
# 动态加载和执行模块函数
|
||||
```
|
||||
|
||||
### 2. PyInstaller配置
|
||||
|
||||
**文件**: `python_core/build.spec`
|
||||
|
||||
配置打包选项:
|
||||
- 隐藏导入:确保所有模块被包含
|
||||
- 数据文件:包含配置和资源文件
|
||||
- 排除模块:减少打包大小
|
||||
- 单文件模式:生成单个可执行文件
|
||||
|
||||
### 3. 自动化构建脚本
|
||||
|
||||
**文件**: `scripts/build_python_core.py`
|
||||
|
||||
提供完整的构建流程:
|
||||
1. 安装构建依赖
|
||||
2. 清理构建目录
|
||||
3. 运行PyInstaller
|
||||
4. 复制到sidecar目录
|
||||
5. 更新Tauri配置
|
||||
6. 验证构建结果
|
||||
|
||||
### 4. Rust代码适配
|
||||
|
||||
**文件**: `src-tauri/src/python_executor.rs`
|
||||
|
||||
添加sidecar支持:
|
||||
- 自动检测sidecar可用性
|
||||
- 开发环境使用系统Python
|
||||
- 生产环境使用sidecar
|
||||
- 统一的API接口
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 构建Python Core
|
||||
|
||||
#### Windows
|
||||
```cmd
|
||||
scripts\build_python_core.bat
|
||||
```
|
||||
|
||||
#### Linux/macOS
|
||||
```bash
|
||||
./scripts/build_python_core.sh
|
||||
```
|
||||
|
||||
#### 手动构建
|
||||
```bash
|
||||
python scripts/build_python_core.py
|
||||
```
|
||||
|
||||
### 开发和测试
|
||||
|
||||
```bash
|
||||
# 开发模式(使用系统Python)
|
||||
cargo tauri dev
|
||||
|
||||
# 生产构建(包含sidecar)
|
||||
cargo tauri build
|
||||
```
|
||||
|
||||
### API调用
|
||||
|
||||
```rust
|
||||
// 新的模块化API
|
||||
use crate::python_executor::execute_python_module_action;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn my_command(app: AppHandle) -> Result<String, String> {
|
||||
let params = serde_json::json!({
|
||||
"folder_path": "/path/to/templates",
|
||||
"force_reimport": false
|
||||
});
|
||||
|
||||
execute_python_module_action(
|
||||
app,
|
||||
"template_manager", // 模块名
|
||||
"batch_import", // 动作名
|
||||
Some(params), // 参数
|
||||
None // 配置
|
||||
).await
|
||||
}
|
||||
```
|
||||
|
||||
## 文件结构
|
||||
|
||||
```
|
||||
project/
|
||||
├── python_core/
|
||||
│ ├── main.py # PyInstaller入口文件
|
||||
│ ├── build.spec # PyInstaller配置
|
||||
│ ├── services/ # 服务模块
|
||||
│ ├── utils/ # 工具模块
|
||||
│ └── ...
|
||||
├── scripts/
|
||||
│ ├── build_python_core.py # 构建脚本
|
||||
│ ├── build_python_core.bat# Windows批处理
|
||||
│ └── build_python_core.sh # Linux/macOS脚本
|
||||
├── src-tauri/
|
||||
│ ├── binaries/ # sidecar可执行文件
|
||||
│ │ └── mixvideo-python-core(.exe)
|
||||
│ ├── src/
|
||||
│ │ └── python_executor.rs # 更新的执行器
|
||||
│ └── tauri.conf.json # 更新的配置
|
||||
└── docs/
|
||||
└── PYTHON_CORE_DISTRIBUTION.md
|
||||
```
|
||||
|
||||
## 配置说明
|
||||
|
||||
### Tauri配置更新
|
||||
|
||||
`src-tauri/tauri.conf.json`:
|
||||
```json
|
||||
{
|
||||
"bundle": {
|
||||
"externalBin": [
|
||||
{
|
||||
"name": "mixvideo-python-core",
|
||||
"src": "binaries/mixvideo-python-core",
|
||||
"targets": ["all"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 环境检测逻辑
|
||||
|
||||
```rust
|
||||
fn should_use_sidecar() -> bool {
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
// 生产环境:检查sidecar是否存在
|
||||
return get_sidecar_path().is_some();
|
||||
}
|
||||
|
||||
// 开发环境:使用系统Python
|
||||
false
|
||||
}
|
||||
```
|
||||
|
||||
## 性能对比
|
||||
|
||||
| 方案 | 启动时间 | 内存占用 | 分发大小 | 依赖管理 |
|
||||
|------|----------|----------|----------|----------|
|
||||
| 系统Python | 慢 | 低 | 小 | 复杂 |
|
||||
| PyInstaller | 快 | 中 | 大 | 简单 |
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 常见问题
|
||||
|
||||
#### 1. 构建失败
|
||||
```bash
|
||||
# 检查Python环境
|
||||
python --version
|
||||
pip --version
|
||||
|
||||
# 安装构建依赖
|
||||
pip install pyinstaller requests Pillow opencv-python-headless
|
||||
```
|
||||
|
||||
#### 2. 模块导入错误
|
||||
- 检查 `build.spec` 中的 `hiddenimports`
|
||||
- 添加缺失的模块到隐藏导入列表
|
||||
|
||||
#### 3. 资源文件缺失
|
||||
- 检查 `build.spec` 中的 `datas` 配置
|
||||
- 确保所有资源文件都被包含
|
||||
|
||||
#### 4. sidecar不可用
|
||||
- 检查 `src-tauri/binaries/` 目录
|
||||
- 验证可执行文件权限
|
||||
- 查看Tauri配置中的sidecar设置
|
||||
|
||||
### 调试方法
|
||||
|
||||
#### 1. 测试sidecar
|
||||
```bash
|
||||
# 直接测试可执行文件
|
||||
./src-tauri/binaries/mixvideo-python-core --version
|
||||
./src-tauri/binaries/mixvideo-python-core --module template_manager --action get_templates
|
||||
```
|
||||
|
||||
#### 2. 查看日志
|
||||
```rust
|
||||
// 在Rust代码中添加调试输出
|
||||
println!("Using sidecar: {}", should_use_sidecar());
|
||||
println!("Sidecar path: {:?}", get_sidecar_path());
|
||||
```
|
||||
|
||||
#### 3. 环境变量
|
||||
```bash
|
||||
# 强制使用系统Python(调试用)
|
||||
export MIXVIDEO_FORCE_SYSTEM_PYTHON=1
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 开发流程
|
||||
1. 在开发环境使用系统Python进行开发和测试
|
||||
2. 定期构建sidecar验证打包正确性
|
||||
3. 在CI/CD中自动化构建流程
|
||||
|
||||
### 2. 版本管理
|
||||
- 为sidecar添加版本信息
|
||||
- 在主应用中检查sidecar版本兼容性
|
||||
- 提供版本不匹配时的降级方案
|
||||
|
||||
### 3. 错误处理
|
||||
- 提供详细的错误信息
|
||||
- 实现自动重试机制
|
||||
- 支持降级到系统Python
|
||||
|
||||
### 4. 性能优化
|
||||
- 使用单文件模式减少启动时间
|
||||
- 排除不必要的模块减少大小
|
||||
- 考虑使用UPX压缩可执行文件
|
||||
|
||||
## 未来改进
|
||||
|
||||
### 短期计划
|
||||
- [ ] 添加自动更新机制
|
||||
- [ ] 支持插件化架构
|
||||
- [ ] 优化启动性能
|
||||
|
||||
### 长期计划
|
||||
- [ ] 考虑使用Rust重写核心逻辑
|
||||
- [ ] 支持多语言sidecar
|
||||
- [ ] 集成云端分发
|
||||
|
||||
---
|
||||
|
||||
通过这个解决方案,我们成功解决了Python代码分发的问题,为用户提供了无需Python环境的一体化体验。
|
||||
133
docs/PYTHON_EMBEDDED_SOLUTION.md
Normal file
133
docs/PYTHON_EMBEDDED_SOLUTION.md
Normal file
@@ -0,0 +1,133 @@
|
||||
# Python嵌入式分发方案(备选)
|
||||
|
||||
## 方案概述
|
||||
|
||||
如果PyInstaller sidecar方案遇到问题,可以考虑直接将Python嵌入式版本打包到应用中。
|
||||
|
||||
## 实现步骤
|
||||
|
||||
### 1. 下载Python嵌入式版本
|
||||
|
||||
```bash
|
||||
# Windows
|
||||
wget https://www.python.org/ftp/python/3.11.0/python-3.11.0-embed-amd64.zip
|
||||
|
||||
# 解压到 src-tauri/python-embed/
|
||||
```
|
||||
|
||||
### 2. 安装依赖到嵌入式Python
|
||||
|
||||
```python
|
||||
# scripts/setup_embedded_python.py
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
def setup_embedded_python():
|
||||
embed_dir = Path("src-tauri/python-embed")
|
||||
python_exe = embed_dir / "python.exe"
|
||||
|
||||
# 安装pip
|
||||
subprocess.run([
|
||||
str(python_exe), "-m", "ensurepip", "--default-pip"
|
||||
])
|
||||
|
||||
# 安装依赖
|
||||
subprocess.run([
|
||||
str(python_exe), "-m", "pip", "install",
|
||||
"requests", "Pillow", "opencv-python-headless"
|
||||
])
|
||||
|
||||
# 复制python_core模块
|
||||
import shutil
|
||||
shutil.copytree("python_core", embed_dir / "python_core")
|
||||
|
||||
if __name__ == "__main__":
|
||||
setup_embedded_python()
|
||||
```
|
||||
|
||||
### 3. 修改Tauri配置
|
||||
|
||||
```json
|
||||
{
|
||||
"bundle": {
|
||||
"resources": [
|
||||
"python-embed/**"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 修改Rust执行器
|
||||
|
||||
```rust
|
||||
fn get_embedded_python_path() -> Option<PathBuf> {
|
||||
let exe_dir = std::env::current_exe().ok()?
|
||||
.parent()?;
|
||||
|
||||
let python_exe = if cfg!(target_os = "windows") {
|
||||
exe_dir.join("python-embed/python.exe")
|
||||
} else {
|
||||
exe_dir.join("python-embed/bin/python3")
|
||||
};
|
||||
|
||||
if python_exe.exists() {
|
||||
Some(python_exe)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
async fn execute_with_embedded_python(
|
||||
module: &str,
|
||||
action: &str,
|
||||
params: Option<&str>,
|
||||
) -> Result<String, String> {
|
||||
let python_exe = get_embedded_python_path()
|
||||
.ok_or("Embedded Python not found")?;
|
||||
|
||||
let mut cmd = Command::new(python_exe);
|
||||
cmd.args(&[
|
||||
"-m", &format!("python_core.{}", module),
|
||||
"--action", action
|
||||
]);
|
||||
|
||||
if let Some(p) = params {
|
||||
cmd.args(&["--params", p]);
|
||||
}
|
||||
|
||||
// 执行命令...
|
||||
}
|
||||
```
|
||||
|
||||
## 优缺点对比
|
||||
|
||||
### PyInstaller Sidecar方案
|
||||
**优点**:
|
||||
- 单文件分发
|
||||
- 启动速度快
|
||||
- 内存占用相对较小
|
||||
|
||||
**缺点**:
|
||||
- 构建复杂
|
||||
- 调试困难
|
||||
- 可能遇到兼容性问题
|
||||
|
||||
### 嵌入式Python方案
|
||||
**优点**:
|
||||
- 构建简单
|
||||
- 调试容易
|
||||
- 兼容性好
|
||||
|
||||
**缺点**:
|
||||
- 分发包较大
|
||||
- 启动速度较慢
|
||||
- 安全性较低
|
||||
|
||||
## 推荐方案
|
||||
|
||||
1. **首选**:PyInstaller Sidecar方案
|
||||
2. **备选**:嵌入式Python方案
|
||||
3. **开发**:系统Python方案
|
||||
|
||||
根据项目需求和技术栈选择最适合的方案。
|
||||
161
python_core/build.spec
Normal file
161
python_core/build.spec
Normal file
@@ -0,0 +1,161 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
"""
|
||||
PyInstaller配置文件 - MixVideo V2 Python Core
|
||||
用于将Python代码打包成独立可执行文件
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# 获取当前目录
|
||||
current_dir = Path(__file__).parent
|
||||
project_root = current_dir.parent
|
||||
|
||||
# 添加隐藏导入(确保所有模块都被包含)
|
||||
hiddenimports = [
|
||||
# 核心模块
|
||||
'python_core',
|
||||
'python_core.config',
|
||||
'python_core.utils',
|
||||
'python_core.utils.logger',
|
||||
'python_core.utils.jsonrpc',
|
||||
'python_core.utils.helpers',
|
||||
'python_core.utils.validators',
|
||||
|
||||
# 服务模块
|
||||
'python_core.services',
|
||||
'python_core.services.template_manager',
|
||||
'python_core.services.project_manager',
|
||||
'python_core.services.media_manager',
|
||||
'python_core.services.audio_manager',
|
||||
'python_core.services.model_manager',
|
||||
'python_core.services.resource_category_manager',
|
||||
'python_core.services.file_manager',
|
||||
|
||||
# AI视频模块
|
||||
'python_core.ai_video',
|
||||
'python_core.ai_video.video_generator',
|
||||
'python_core.ai_video.api_client',
|
||||
'python_core.ai_video.cloud_storage',
|
||||
|
||||
# 处理模块
|
||||
'python_core.audio_processing',
|
||||
'python_core.audio_processing.core',
|
||||
'python_core.video_processing',
|
||||
'python_core.video_processing.core',
|
||||
|
||||
# 第三方依赖
|
||||
'requests',
|
||||
'PIL',
|
||||
'Pillow',
|
||||
'numpy',
|
||||
'opencv-python',
|
||||
'cv2',
|
||||
'json',
|
||||
'sqlite3',
|
||||
'pathlib',
|
||||
'uuid',
|
||||
'hashlib',
|
||||
'base64',
|
||||
'urllib',
|
||||
'urllib.parse',
|
||||
'urllib.request',
|
||||
'http',
|
||||
'http.client',
|
||||
'ssl',
|
||||
'certifi',
|
||||
]
|
||||
|
||||
# 数据文件(包含配置文件、模板等)
|
||||
datas = [
|
||||
# 配置文件
|
||||
(str(current_dir / '*.json'), '.'),
|
||||
(str(current_dir / '*.yaml'), '.'),
|
||||
(str(current_dir / '*.yml'), '.'),
|
||||
|
||||
# 模板文件
|
||||
(str(project_root / 'assets' / 'templates'), 'assets/templates'),
|
||||
|
||||
# 其他资源文件
|
||||
(str(project_root / 'assets' / 'icons'), 'assets/icons'),
|
||||
]
|
||||
|
||||
# 二进制文件(如果有的话)
|
||||
binaries = []
|
||||
|
||||
# 排除的模块(减少打包大小)
|
||||
excludes = [
|
||||
'tkinter',
|
||||
'matplotlib',
|
||||
'scipy',
|
||||
'pandas',
|
||||
'jupyter',
|
||||
'notebook',
|
||||
'IPython',
|
||||
'pytest',
|
||||
'unittest',
|
||||
'test',
|
||||
'tests',
|
||||
'_pytest',
|
||||
'setuptools',
|
||||
'pip',
|
||||
'wheel',
|
||||
'distutils',
|
||||
]
|
||||
|
||||
# 分析配置
|
||||
a = Analysis(
|
||||
[str(current_dir / 'main.py')],
|
||||
pathex=[str(current_dir), str(project_root)],
|
||||
binaries=binaries,
|
||||
datas=datas,
|
||||
hiddenimports=hiddenimports,
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=excludes,
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=None,
|
||||
noarchive=False,
|
||||
)
|
||||
|
||||
# 处理PYZ
|
||||
pyz = PYZ(a.pure, a.zipped_data, cipher=None)
|
||||
|
||||
# 可执行文件配置
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
[],
|
||||
name='mixvideo-python-core',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=True, # 保持控制台模式,便于调试
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
icon=str(project_root / 'src-tauri' / 'icons' / 'icon.ico') if (project_root / 'src-tauri' / 'icons' / 'icon.ico').exists() else None,
|
||||
)
|
||||
|
||||
# 如果需要创建目录分发版本(可选)
|
||||
# coll = COLLECT(
|
||||
# exe,
|
||||
# a.binaries,
|
||||
# a.zipfiles,
|
||||
# a.datas,
|
||||
# strip=False,
|
||||
# upx=True,
|
||||
# upx_exclude=[],
|
||||
# name='mixvideo-python-core'
|
||||
# )
|
||||
208
python_core/main.py
Normal file
208
python_core/main.py
Normal file
@@ -0,0 +1,208 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
MixVideo V2 Python Core - Main Entry Point
|
||||
用于PyInstaller打包的主入口文件
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import argparse
|
||||
import json
|
||||
import traceback
|
||||
from pathlib import Path
|
||||
|
||||
# 添加当前目录到Python路径,确保可以导入python_core模块
|
||||
current_dir = Path(__file__).parent
|
||||
sys.path.insert(0, str(current_dir))
|
||||
|
||||
# 导入核心模块
|
||||
try:
|
||||
from utils.logger import get_logger
|
||||
from utils.jsonrpc import create_response_handler, create_progress_reporter
|
||||
from config import get_config
|
||||
except ImportError as e:
|
||||
print(f"ERROR: Failed to import core modules: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# 模块映射表
|
||||
MODULE_MAP = {
|
||||
# 模板管理
|
||||
'template_manager': 'services.template_manager',
|
||||
'template': 'services.template_manager',
|
||||
|
||||
# 项目管理
|
||||
'project_manager': 'services.project_manager',
|
||||
'project': 'services.project_manager',
|
||||
|
||||
# 媒体管理
|
||||
'media_manager': 'services.media_manager',
|
||||
'media': 'services.media_manager',
|
||||
|
||||
# 音频管理
|
||||
'audio_manager': 'services.audio_manager',
|
||||
'audio': 'services.audio_manager',
|
||||
|
||||
# 模特管理
|
||||
'model_manager': 'services.model_manager',
|
||||
'model': 'services.model_manager',
|
||||
|
||||
# 资源分类管理
|
||||
'resource_category_manager': 'services.resource_category_manager',
|
||||
'category': 'services.resource_category_manager',
|
||||
|
||||
# 文件管理
|
||||
'file_manager': 'services.file_manager',
|
||||
'file': 'services.file_manager',
|
||||
|
||||
# AI视频生成
|
||||
'ai_video': 'ai_video.video_generator',
|
||||
'video_generator': 'ai_video.video_generator',
|
||||
|
||||
# 音频处理
|
||||
'audio_processing': 'audio_processing.core',
|
||||
|
||||
# 视频处理
|
||||
'video_processing': 'video_processing.core',
|
||||
}
|
||||
|
||||
def setup_logging():
|
||||
"""设置日志"""
|
||||
try:
|
||||
logger = get_logger(__name__)
|
||||
return logger
|
||||
except Exception as e:
|
||||
print(f"WARNING: Failed to setup logging: {e}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
def load_module_function(module_name, function_name):
|
||||
"""动态加载模块和函数"""
|
||||
try:
|
||||
# 解析模块路径
|
||||
if module_name in MODULE_MAP:
|
||||
module_path = MODULE_MAP[module_name]
|
||||
else:
|
||||
module_path = module_name
|
||||
|
||||
# 导入模块
|
||||
module = __import__(f"python_core.{module_path}", fromlist=[function_name])
|
||||
|
||||
# 获取函数
|
||||
if hasattr(module, function_name):
|
||||
return getattr(module, function_name)
|
||||
else:
|
||||
raise AttributeError(f"Function '{function_name}' not found in module '{module_path}'")
|
||||
|
||||
except ImportError as e:
|
||||
raise ImportError(f"Failed to import module '{module_path}': {e}")
|
||||
except Exception as e:
|
||||
raise Exception(f"Failed to load function '{function_name}' from '{module_path}': {e}")
|
||||
|
||||
def execute_action(module_name, action, params=None, progress_callback=None):
|
||||
"""执行指定的动作"""
|
||||
logger = setup_logging()
|
||||
|
||||
try:
|
||||
if logger:
|
||||
logger.info(f"Executing action: {module_name}.{action}")
|
||||
|
||||
# 加载函数
|
||||
func = load_module_function(module_name, action)
|
||||
|
||||
# 准备参数
|
||||
kwargs = {}
|
||||
if params:
|
||||
kwargs.update(params)
|
||||
|
||||
# 添加进度回调
|
||||
if progress_callback:
|
||||
kwargs['progress_callback'] = progress_callback
|
||||
|
||||
# 执行函数
|
||||
result = func(**kwargs)
|
||||
|
||||
if logger:
|
||||
logger.info(f"Action completed successfully: {module_name}.{action}")
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Failed to execute {module_name}.{action}: {str(e)}"
|
||||
if logger:
|
||||
logger.error(error_msg)
|
||||
logger.error(traceback.format_exc())
|
||||
raise Exception(error_msg)
|
||||
|
||||
def parse_arguments():
|
||||
"""解析命令行参数"""
|
||||
parser = argparse.ArgumentParser(description='MixVideo V2 Python Core')
|
||||
|
||||
parser.add_argument('--module', '-m', required=True,
|
||||
help='Module name (e.g., template_manager, project_manager)')
|
||||
|
||||
parser.add_argument('--action', '-a', required=True,
|
||||
help='Action to execute (e.g., get_templates, batch_import)')
|
||||
|
||||
parser.add_argument('--params', '-p', type=str,
|
||||
help='Parameters as JSON string')
|
||||
|
||||
parser.add_argument('--config', '-c', type=str,
|
||||
help='Configuration file path')
|
||||
|
||||
parser.add_argument('--verbose', '-v', action='store_true',
|
||||
help='Enable verbose logging')
|
||||
|
||||
parser.add_argument('--version', action='version', version='MixVideo V2 Python Core 2.0.0')
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
try:
|
||||
# 解析参数
|
||||
args = parse_arguments()
|
||||
|
||||
# 设置日志级别
|
||||
if args.verbose:
|
||||
os.environ['LOG_LEVEL'] = 'DEBUG'
|
||||
|
||||
# 加载配置
|
||||
if args.config:
|
||||
os.environ['CONFIG_FILE'] = args.config
|
||||
|
||||
# 解析参数
|
||||
params = {}
|
||||
if args.params:
|
||||
try:
|
||||
params = json.loads(args.params)
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"ERROR: Invalid JSON parameters: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# 创建响应处理器
|
||||
send_response = create_response_handler()
|
||||
send_progress = create_progress_reporter()
|
||||
|
||||
# 创建进度回调
|
||||
def progress_callback(step, progress, message, details=None):
|
||||
send_progress(step, progress, message, details or {})
|
||||
|
||||
# 执行动作
|
||||
result = execute_action(args.module, args.action, params, progress_callback)
|
||||
|
||||
# 发送成功响应
|
||||
send_response(True, "操作完成", result)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("ERROR: Operation cancelled by user", file=sys.stderr)
|
||||
sys.exit(130)
|
||||
except Exception as e:
|
||||
# 发送错误响应
|
||||
try:
|
||||
send_response = create_response_handler()
|
||||
send_response(False, str(e), None)
|
||||
except:
|
||||
print(f"ERROR: {str(e)}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
46
scripts/build_python_core.bat
Normal file
46
scripts/build_python_core.bat
Normal file
@@ -0,0 +1,46 @@
|
||||
@echo off
|
||||
REM Windows批处理脚本 - 构建Python Core
|
||||
REM 用于Windows环境下构建Python可执行文件
|
||||
|
||||
echo 🚀 Building MixVideo V2 Python Core for Windows...
|
||||
|
||||
REM 检查Python是否安装
|
||||
python --version >nul 2>&1
|
||||
if errorlevel 1 (
|
||||
echo ❌ Python is not installed or not in PATH
|
||||
echo Please install Python 3.8+ and add it to PATH
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM 检查pip是否可用
|
||||
python -m pip --version >nul 2>&1
|
||||
if errorlevel 1 (
|
||||
echo ❌ pip is not available
|
||||
echo Please ensure pip is installed with Python
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo ✅ Python environment check passed
|
||||
|
||||
REM 切换到项目根目录
|
||||
cd /d "%~dp0.."
|
||||
|
||||
REM 运行Python构建脚本
|
||||
echo 📦 Running Python build script...
|
||||
python scripts\build_python_core.py
|
||||
|
||||
if errorlevel 1 (
|
||||
echo ❌ Build failed
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo ✅ Build completed successfully!
|
||||
echo.
|
||||
echo 📋 Next steps:
|
||||
echo 1. Test the build: cargo tauri dev
|
||||
echo 2. Build the app: cargo tauri build
|
||||
echo.
|
||||
pause
|
||||
257
scripts/build_python_core.py
Normal file
257
scripts/build_python_core.py
Normal file
@@ -0,0 +1,257 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
构建脚本 - 将Python Core打包成独立可执行文件
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import shutil
|
||||
import platform
|
||||
from pathlib import Path
|
||||
|
||||
def get_project_root():
|
||||
"""获取项目根目录"""
|
||||
current_file = Path(__file__).resolve()
|
||||
# 从 scripts/build_python_core.py 回到项目根目录
|
||||
return current_file.parent.parent
|
||||
|
||||
def install_dependencies():
|
||||
"""安装构建依赖"""
|
||||
print("📦 Installing build dependencies...")
|
||||
|
||||
dependencies = [
|
||||
'pyinstaller>=5.0',
|
||||
'requests',
|
||||
'Pillow',
|
||||
'opencv-python-headless', # 使用headless版本减少依赖
|
||||
]
|
||||
|
||||
for dep in dependencies:
|
||||
print(f"Installing {dep}...")
|
||||
result = subprocess.run([
|
||||
sys.executable, '-m', 'pip', 'install', dep
|
||||
], capture_output=True, text=True)
|
||||
|
||||
if result.returncode != 0:
|
||||
print(f"❌ Failed to install {dep}")
|
||||
print(result.stderr)
|
||||
return False
|
||||
else:
|
||||
print(f"✅ Installed {dep}")
|
||||
|
||||
return True
|
||||
|
||||
def clean_build_directory(python_core_dir):
|
||||
"""清理构建目录"""
|
||||
print("🧹 Cleaning build directories...")
|
||||
|
||||
dirs_to_clean = ['build', 'dist', '__pycache__']
|
||||
|
||||
for dir_name in dirs_to_clean:
|
||||
dir_path = python_core_dir / dir_name
|
||||
if dir_path.exists():
|
||||
shutil.rmtree(dir_path)
|
||||
print(f"Removed {dir_path}")
|
||||
|
||||
def build_executable(python_core_dir):
|
||||
"""构建可执行文件"""
|
||||
print("🔨 Building executable...")
|
||||
|
||||
spec_file = python_core_dir / 'build.spec'
|
||||
|
||||
if not spec_file.exists():
|
||||
print(f"❌ Spec file not found: {spec_file}")
|
||||
return False
|
||||
|
||||
# 运行PyInstaller
|
||||
cmd = [
|
||||
sys.executable, '-m', 'PyInstaller',
|
||||
'--clean',
|
||||
'--noconfirm',
|
||||
str(spec_file)
|
||||
]
|
||||
|
||||
print(f"Running: {' '.join(cmd)}")
|
||||
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
cwd=python_core_dir,
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
print("❌ Build failed!")
|
||||
print("STDOUT:", result.stdout)
|
||||
print("STDERR:", result.stderr)
|
||||
return False
|
||||
|
||||
print("✅ Build completed successfully!")
|
||||
return True
|
||||
|
||||
def copy_to_sidecar_directory(project_root, python_core_dir):
|
||||
"""复制可执行文件到Tauri sidecar目录"""
|
||||
print("📁 Copying executable to sidecar directory...")
|
||||
|
||||
# 确定可执行文件名
|
||||
system = platform.system().lower()
|
||||
if system == 'windows':
|
||||
exe_name = 'mixvideo-python-core.exe'
|
||||
else:
|
||||
exe_name = 'mixvideo-python-core'
|
||||
|
||||
# 源文件路径
|
||||
source_exe = python_core_dir / 'dist' / exe_name
|
||||
|
||||
if not source_exe.exists():
|
||||
print(f"❌ Executable not found: {source_exe}")
|
||||
return False
|
||||
|
||||
# 目标目录
|
||||
sidecar_dir = project_root / 'src-tauri' / 'binaries'
|
||||
sidecar_dir.mkdir(exist_ok=True)
|
||||
|
||||
# 复制文件
|
||||
target_exe = sidecar_dir / exe_name
|
||||
shutil.copy2(source_exe, target_exe)
|
||||
|
||||
# 在Unix系统上设置执行权限
|
||||
if system != 'windows':
|
||||
os.chmod(target_exe, 0o755)
|
||||
|
||||
print(f"✅ Copied to: {target_exe}")
|
||||
return True
|
||||
|
||||
def update_tauri_config(project_root):
|
||||
"""更新Tauri配置文件以包含sidecar"""
|
||||
print("⚙️ Updating Tauri configuration...")
|
||||
|
||||
import json
|
||||
|
||||
config_file = project_root / 'src-tauri' / 'tauri.conf.json'
|
||||
|
||||
if not config_file.exists():
|
||||
print(f"❌ Tauri config not found: {config_file}")
|
||||
return False
|
||||
|
||||
# 读取配置
|
||||
with open(config_file, 'r', encoding='utf-8') as f:
|
||||
config = json.load(f)
|
||||
|
||||
# 添加sidecar配置
|
||||
if 'bundle' not in config:
|
||||
config['bundle'] = {}
|
||||
|
||||
if 'externalBin' not in config['bundle']:
|
||||
config['bundle']['externalBin'] = []
|
||||
|
||||
# 检查是否已经存在
|
||||
sidecar_name = 'mixvideo-python-core'
|
||||
existing = any(
|
||||
item.get('name') == sidecar_name
|
||||
for item in config['bundle']['externalBin']
|
||||
if isinstance(item, dict)
|
||||
)
|
||||
|
||||
if not existing:
|
||||
config['bundle']['externalBin'].append({
|
||||
"name": sidecar_name,
|
||||
"src": f"binaries/{sidecar_name}",
|
||||
"targets": ["all"]
|
||||
})
|
||||
|
||||
# 写回配置
|
||||
with open(config_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(config, f, indent=2, ensure_ascii=False)
|
||||
|
||||
print("✅ Updated Tauri configuration")
|
||||
else:
|
||||
print("ℹ️ Sidecar already configured in Tauri")
|
||||
|
||||
return True
|
||||
|
||||
def verify_build(project_root):
|
||||
"""验证构建结果"""
|
||||
print("🔍 Verifying build...")
|
||||
|
||||
system = platform.system().lower()
|
||||
exe_name = 'mixvideo-python-core.exe' if system == 'windows' else 'mixvideo-python-core'
|
||||
|
||||
sidecar_exe = project_root / 'src-tauri' / 'binaries' / exe_name
|
||||
|
||||
if not sidecar_exe.exists():
|
||||
print(f"❌ Sidecar executable not found: {sidecar_exe}")
|
||||
return False
|
||||
|
||||
# 测试执行
|
||||
try:
|
||||
result = subprocess.run([
|
||||
str(sidecar_exe), '--version'
|
||||
], capture_output=True, text=True, timeout=10)
|
||||
|
||||
if result.returncode == 0:
|
||||
print("✅ Executable is working correctly")
|
||||
print(f"Version output: {result.stdout.strip()}")
|
||||
return True
|
||||
else:
|
||||
print(f"❌ Executable test failed: {result.stderr}")
|
||||
return False
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
print("❌ Executable test timed out")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"❌ Executable test error: {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("🚀 Building MixVideo V2 Python Core...")
|
||||
|
||||
project_root = get_project_root()
|
||||
python_core_dir = project_root / 'python_core'
|
||||
|
||||
print(f"Project root: {project_root}")
|
||||
print(f"Python core directory: {python_core_dir}")
|
||||
|
||||
if not python_core_dir.exists():
|
||||
print(f"❌ Python core directory not found: {python_core_dir}")
|
||||
sys.exit(1)
|
||||
|
||||
# 步骤1: 安装依赖
|
||||
if not install_dependencies():
|
||||
print("❌ Failed to install dependencies")
|
||||
sys.exit(1)
|
||||
|
||||
# 步骤2: 清理构建目录
|
||||
clean_build_directory(python_core_dir)
|
||||
|
||||
# 步骤3: 构建可执行文件
|
||||
if not build_executable(python_core_dir):
|
||||
print("❌ Failed to build executable")
|
||||
sys.exit(1)
|
||||
|
||||
# 步骤4: 复制到sidecar目录
|
||||
if not copy_to_sidecar_directory(project_root, python_core_dir):
|
||||
print("❌ Failed to copy executable")
|
||||
sys.exit(1)
|
||||
|
||||
# 步骤5: 更新Tauri配置
|
||||
if not update_tauri_config(project_root):
|
||||
print("❌ Failed to update Tauri configuration")
|
||||
sys.exit(1)
|
||||
|
||||
# 步骤6: 验证构建
|
||||
if not verify_build(project_root):
|
||||
print("❌ Build verification failed")
|
||||
sys.exit(1)
|
||||
|
||||
print("🎉 Build completed successfully!")
|
||||
print("\n📋 Next steps:")
|
||||
print("1. Test the sidecar: cargo tauri dev")
|
||||
print("2. Build the app: cargo tauri build")
|
||||
print("3. The Python core will be bundled with your app")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
53
scripts/build_python_core.sh
Executable file
53
scripts/build_python_core.sh
Executable file
@@ -0,0 +1,53 @@
|
||||
#!/bin/bash
|
||||
# Shell脚本 - 构建Python Core
|
||||
# 用于Linux/macOS环境下构建Python可执行文件
|
||||
|
||||
set -e # 遇到错误时退出
|
||||
|
||||
echo "🚀 Building MixVideo V2 Python Core for $(uname)..."
|
||||
|
||||
# 检查Python是否安装
|
||||
if ! command -v python3 &> /dev/null; then
|
||||
if ! command -v python &> /dev/null; then
|
||||
echo "❌ Python is not installed or not in PATH"
|
||||
echo "Please install Python 3.8+ and add it to PATH"
|
||||
exit 1
|
||||
else
|
||||
PYTHON_CMD="python"
|
||||
fi
|
||||
else
|
||||
PYTHON_CMD="python3"
|
||||
fi
|
||||
|
||||
echo "✅ Using Python: $PYTHON_CMD"
|
||||
|
||||
# 检查Python版本
|
||||
PYTHON_VERSION=$($PYTHON_CMD --version 2>&1 | cut -d' ' -f2)
|
||||
echo "Python version: $PYTHON_VERSION"
|
||||
|
||||
# 检查pip是否可用
|
||||
if ! $PYTHON_CMD -m pip --version &> /dev/null; then
|
||||
echo "❌ pip is not available"
|
||||
echo "Please ensure pip is installed with Python"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Python environment check passed"
|
||||
|
||||
# 切换到项目根目录
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
echo "📁 Working directory: $PROJECT_ROOT"
|
||||
|
||||
# 运行Python构建脚本
|
||||
echo "📦 Running Python build script..."
|
||||
$PYTHON_CMD scripts/build_python_core.py
|
||||
|
||||
echo "✅ Build completed successfully!"
|
||||
echo ""
|
||||
echo "📋 Next steps:"
|
||||
echo "1. Test the build: cargo tauri dev"
|
||||
echo "2. Build the app: cargo tauri build"
|
||||
echo ""
|
||||
@@ -5,9 +5,112 @@ use std::thread;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::mpsc;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tauri::{AppHandle, Emitter};
|
||||
use tauri::{AppHandle, Emitter, Manager};
|
||||
use crate::command_utils::configure_python_command;
|
||||
|
||||
/// Check if we should use the bundled Python sidecar
|
||||
fn should_use_sidecar() -> bool {
|
||||
// 在生产环境中优先使用sidecar
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
// 检查sidecar可执行文件是否存在
|
||||
let exe_name = if cfg!(target_os = "windows") {
|
||||
"mixvideo-python-core.exe"
|
||||
} else {
|
||||
"mixvideo-python-core"
|
||||
};
|
||||
|
||||
// 检查当前目录和常见位置
|
||||
let possible_paths = vec![
|
||||
std::env::current_exe().ok()
|
||||
.and_then(|p| p.parent().map(|p| p.join(exe_name))),
|
||||
Some(std::path::PathBuf::from(exe_name)),
|
||||
];
|
||||
|
||||
for path in possible_paths.into_iter().flatten() {
|
||||
if path.exists() {
|
||||
println!("Found sidecar at: {:?}", path);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 在开发环境中或sidecar不可用时使用系统Python
|
||||
false
|
||||
}
|
||||
|
||||
/// Get the path to the Python sidecar executable
|
||||
fn get_sidecar_path() -> Option<std::path::PathBuf> {
|
||||
let exe_name = if cfg!(target_os = "windows") {
|
||||
"mixvideo-python-core.exe"
|
||||
} else {
|
||||
"mixvideo-python-core"
|
||||
};
|
||||
|
||||
// 检查可能的路径
|
||||
let possible_paths = vec![
|
||||
// 与主程序同目录
|
||||
std::env::current_exe().ok()
|
||||
.and_then(|p| p.parent().map(|p| p.join(exe_name))),
|
||||
// 当前工作目录
|
||||
Some(std::path::PathBuf::from(exe_name)),
|
||||
// 相对路径
|
||||
Some(std::path::PathBuf::from("./").join(exe_name)),
|
||||
];
|
||||
|
||||
for path in possible_paths.into_iter().flatten() {
|
||||
if path.exists() {
|
||||
return Some(path);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Execute Python command using sidecar
|
||||
async fn execute_python_sidecar(
|
||||
module: &str,
|
||||
action: &str,
|
||||
params: Option<&str>,
|
||||
config: Option<PythonExecutorConfig>,
|
||||
) -> Result<String, String> {
|
||||
let _config = config.unwrap_or_default();
|
||||
|
||||
println!("Executing Python sidecar: module={}, action={}", module, action);
|
||||
|
||||
// 获取sidecar路径
|
||||
let sidecar_path = get_sidecar_path()
|
||||
.ok_or_else(|| "Sidecar executable not found".to_string())?;
|
||||
|
||||
// 构建命令
|
||||
let mut cmd = Command::new(&sidecar_path);
|
||||
cmd.args(&["--module", module])
|
||||
.args(&["--action", action]);
|
||||
|
||||
if let Some(params_str) = params {
|
||||
cmd.args(&["--params", params_str]);
|
||||
}
|
||||
|
||||
// 配置命令(隐藏控制台窗口等)
|
||||
crate::command_utils::configure_no_window(&mut cmd);
|
||||
|
||||
cmd.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::piped());
|
||||
|
||||
// 执行命令
|
||||
let output = cmd.output()
|
||||
.map_err(|e| format!("Failed to execute sidecar: {}", e))?;
|
||||
|
||||
if output.status.success() {
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
println!("Sidecar output: {}", stdout);
|
||||
Ok(stdout.to_string())
|
||||
} else {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
Err(format!("Sidecar failed: {}", stderr))
|
||||
}
|
||||
}
|
||||
|
||||
/// Generic progress information from Python processes
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct PythonProgress {
|
||||
@@ -147,6 +250,49 @@ pub async fn execute_python_with_events(
|
||||
execute_python_command_with_progress(app, args, config, Some(progress_callback)).await
|
||||
}
|
||||
|
||||
/// Execute Python module action with automatic sidecar/system Python selection
|
||||
pub async fn execute_python_module_action(
|
||||
app: tauri::AppHandle,
|
||||
module: &str,
|
||||
action: &str,
|
||||
params: Option<serde_json::Value>,
|
||||
config: Option<PythonExecutorConfig>,
|
||||
) -> Result<String, String> {
|
||||
// 检查是否应该使用sidecar
|
||||
if should_use_sidecar() {
|
||||
println!("Using Python sidecar for execution");
|
||||
|
||||
// 将参数转换为JSON字符串
|
||||
let params_str = if let Some(p) = params {
|
||||
Some(serde_json::to_string(&p)
|
||||
.map_err(|e| format!("Failed to serialize parameters: {}", e))?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
execute_python_sidecar(module, action, params_str.as_deref(), config).await
|
||||
} else {
|
||||
println!("Using system Python for execution");
|
||||
|
||||
// 构建传统的命令行参数
|
||||
let mut args = vec![
|
||||
"-m".to_string(),
|
||||
format!("python_core.{}", module),
|
||||
"--action".to_string(),
|
||||
action.to_string(),
|
||||
];
|
||||
|
||||
// 添加参数
|
||||
if let Some(p) = params {
|
||||
args.push("--params".to_string());
|
||||
args.push(serde_json::to_string(&p)
|
||||
.map_err(|e| format!("Failed to serialize parameters: {}", e))?);
|
||||
}
|
||||
|
||||
execute_python_command(app, &args, config).await
|
||||
}
|
||||
}
|
||||
|
||||
/// Python command builder for easier command construction
|
||||
#[allow(dead_code)]
|
||||
pub struct PythonCommandBuilder {
|
||||
|
||||
Reference in New Issue
Block a user