diff --git a/AGENTS.md b/AGENTS.md index 4fdd39e..1ab32a1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,7 +9,7 @@ **美家卡智影**是一款面向桌面端的 AI 视频创作应用,采用"Python 后端 API + Tauri 桌面前端"的混合架构。 - **产品标识**: `cn.meijiaka.ai-video` / `cn.meijiaka.ai-zy` -- **版本**: `1.8.1` +- **版本**: `1.8.2` - **核心功能**: AI 脚本生成、AI 配音合成(TTS)、声音复刻、视频生成(Vidu)、视频字幕生成、压制成片(FFmpeg)、项目本地持久化 ### 技术栈总览 diff --git a/VERSION b/VERSION index a8fdfda..53adb84 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.8.1 +1.8.2 diff --git a/python-api/.env.example b/python-api/.env.example index 19274d5..64ae174 100644 --- a/python-api/.env.example +++ b/python-api/.env.example @@ -4,7 +4,7 @@ # === 基础配置 === APP_NAME=美家卡智影 API -APP_VERSION=1.8.1 +APP_VERSION=1.8.2 # ⚠️ 生产环境必须设为 false DEBUG=true ENV=development diff --git a/python-api/app/config.py b/python-api/app/config.py index 41b79c7..63583e7 100644 --- a/python-api/app/config.py +++ b/python-api/app/config.py @@ -24,7 +24,7 @@ class Settings(BaseSettings): # 应用基础配置 APP_NAME: str = Field(default="美家卡智影 API", description="应用名称") - APP_VERSION: str = Field(default="1.8.1", description="应用版本") + APP_VERSION: str = Field(default="1.8.2", description="应用版本") DEBUG: bool = Field(default=False, description="调试模式") ENV: Literal["development", "staging", "production"] = Field( default="development", description="运行环境" diff --git a/python-api/pyproject.toml b/python-api/pyproject.toml index 29281cd..c8f126e 100644 --- a/python-api/pyproject.toml +++ b/python-api/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "meijiaka-ai-api" -version = "1.8.1" +version = "1.8.2" description = "美家卡智影 - AI 视频创作后端 API" authors = [{ name = "Meijiaka Team" }] readme = "README.md" diff --git a/python-api/uv.lock b/python-api/uv.lock index 0bf716b..b8d8882 100644 --- a/python-api/uv.lock +++ b/python-api/uv.lock @@ -944,7 +944,7 @@ wheels = [ [[package]] name = "meijiaka-ai-api" -version = "1.8.1" +version = "1.8.2" source = { virtual = "." } dependencies = [ { name = "aiohttp" }, diff --git a/tauri-app/AGENTS.md b/tauri-app/AGENTS.md index e4afd3e..f0735da 100644 --- a/tauri-app/AGENTS.md +++ b/tauri-app/AGENTS.md @@ -9,7 +9,7 @@ **美家卡智影**(产品名)是一款基于 Tauri v2 + React 19 + TypeScript 的桌面端 AI 视频创作应用。 - **产品标识**: `cn.meijiaka.ai-video` -- **版本**: `1.8.1` +- **版本**: `1.8.2` - **窗口尺寸**: 1200×800,不可缩放(`resizable: false`) - **核心功能**: AI 脚本生成、AI 配音合成、视频生成、压制成片(FFmpeg)、项目本地持久化 diff --git a/tauri-app/package-lock.json b/tauri-app/package-lock.json index 53529f3..88a541e 100644 --- a/tauri-app/package-lock.json +++ b/tauri-app/package-lock.json @@ -1,12 +1,12 @@ { "name": "tauri-app", - "version": "1.8.1", + "version": "1.8.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "tauri-app", - "version": "1.8.1", + "version": "1.8.2", "dependencies": { "@microsoft/fetch-event-source": "^2.0.1", "@tanstack/react-virtual": "^3.13.23", diff --git a/tauri-app/package.json b/tauri-app/package.json index ac97ccc..2ed2ed1 100644 --- a/tauri-app/package.json +++ b/tauri-app/package.json @@ -1,7 +1,7 @@ { "name": "tauri-app", "private": true, - "version": "1.8.1", + "version": "1.8.2", "type": "module", "scripts": { "dev": "vite", diff --git a/tauri-app/src-tauri/Cargo.lock b/tauri-app/src-tauri/Cargo.lock index 51dc1cf..9b6b4d0 100644 --- a/tauri-app/src-tauri/Cargo.lock +++ b/tauri-app/src-tauri/Cargo.lock @@ -4219,7 +4219,7 @@ dependencies = [ [[package]] name = "tauri-app" -version = "1.8.1" +version = "1.8.2" dependencies = [ "base64 0.22.1", "chrono", diff --git a/tauri-app/src-tauri/Cargo.toml b/tauri-app/src-tauri/Cargo.toml index bc26989..2dc93f9 100644 --- a/tauri-app/src-tauri/Cargo.toml +++ b/tauri-app/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tauri-app" -version = "1.8.1" +version = "1.8.2" description = "美家卡智影 - AI 视频创作桌面应用" authors = ["美家卡科技"] edition = "2021" diff --git a/tauri-app/src-tauri/src/commands/video_compose.rs b/tauri-app/src-tauri/src/commands/video_compose.rs index 14f245b..6dad8e3 100644 --- a/tauri-app/src-tauri/src/commands/video_compose.rs +++ b/tauri-app/src-tauri/src/commands/video_compose.rs @@ -55,20 +55,24 @@ fn validate_nine_sixteen_aspect(width: u32, height: u32) -> Result<(), String> { Ok(()) } -/// 根据素材最小高度决定目标输出分辨率 +/// 根据素材最大高度决定目标输出分辨率 +/// +/// 向上统一到最高素材的分辨率:只要存在 1080p 素材,成片就输出 1080p, +/// 避免高分辨率素材被强制压缩到 720p 导致画面模糊。 +/// 720p 素材会被放大拼接,虽然略有损失,但优于整体降质。 fn resolve_target_resolution(heights: &[u32]) -> Option<(u32, u32)> { - let min_height = heights.iter().min()?; - if *min_height <= 1280 { - Some((720, 1280)) - } else { + let max_height = heights.iter().max()?; + if *max_height >= 1920 { Some((1080, 1920)) + } else { + Some((720, 1280)) } } /// 拼接视频片段 /// /// 1. 探测所有片段分辨率并校验 9:16 比例 -/// 2. 按最小高度决定目标输出分辨率 +/// 2. 按最大高度决定目标输出分辨率(向上统一,避免高分辨率素材被压糊) /// 3. 统一标准化后拼接 #[tauri::command] pub async fn concat_video_clips( diff --git a/tauri-app/src-tauri/tauri.conf.json b/tauri-app/src-tauri/tauri.conf.json index fb29455..50b7c36 100644 --- a/tauri-app/src-tauri/tauri.conf.json +++ b/tauri-app/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "美家卡智影", - "version": "1.8.1", + "version": "1.8.2", "identifier": "cn.meijiaka.ai-zy", "build": { "beforeDevCommand": "npm run dev", diff --git a/tauri-app/src/pages/ContentManagement/VoiceMaterialLibrary.tsx b/tauri-app/src/pages/ContentManagement/VoiceMaterialLibrary.tsx index eb0d516..0bfbb4e 100644 --- a/tauri-app/src/pages/ContentManagement/VoiceMaterialLibrary.tsx +++ b/tauri-app/src/pages/ContentManagement/VoiceMaterialLibrary.tsx @@ -143,7 +143,7 @@ export default function VoiceMaterialLibrary() { // 音频文件验证 const validateAudioFile = (file: File): Promise<{ valid: boolean; error?: string }> => { return new Promise(resolve => { - const maxSize = 20 * 1024 * 1024; // 20MB + const maxSize = 50 * 1024 * 1024; // 50MB if (file.size > maxSize) { resolve({ valid: false, error: `文件大小 ${(file.size / 1024 / 1024).toFixed(1)}MB,要求不超过 20MB` }); return; @@ -260,6 +260,7 @@ export default function VoiceMaterialLibrary() { progress.update('正在生成专属音色...'); progress.success('复刻成功', 200); } catch (err) { + console.error('[VoiceMaterialLibrary] handleUpload catch:', err, '类型:', typeof err, '是Error?', err instanceof Error); const msg = err instanceof Error ? err.message : '上传失败'; if (msg.includes('402') || msg.includes('积分不足')) { setShowPointsModal(true); @@ -419,7 +420,7 @@ export default function VoiceMaterialLibrary() {
点击选择文件
支持 MP3 / M4A / WAV / MP4
-
人声干净无杂音,时长 10 秒 ~ 2 分钟,不超过 20MB
+
人声干净无杂音,时长 10 秒 ~ 2 分钟,不超过 50MB
)} diff --git a/tauri-app/src/store/voiceStore.ts b/tauri-app/src/store/voiceStore.ts index de67169..2dcc19a 100644 --- a/tauri-app/src/store/voiceStore.ts +++ b/tauri-app/src/store/voiceStore.ts @@ -8,7 +8,7 @@ import { create } from 'zustand'; import type { VoiceInfo, VoiceMaterial } from '../api/modules/voice'; import * as voiceApi from '../api/modules/voice'; -import { writeFile, remove, BaseDirectory } from '@tauri-apps/plugin-fs'; +import { writeFile, remove, mkdir, BaseDirectory } from '@tauri-apps/plugin-fs'; import { join, appLocalDataDir } from '@tauri-apps/api/path'; interface VoiceState { @@ -136,33 +136,45 @@ export const useVoiceStore = create()( try { const ext = file.name.substring(file.name.lastIndexOf('.')).toLowerCase(); + console.log('[VoiceStore] addVoiceMaterial 开始, ext=', ext, 'fileName=', file.name, 'size=', file.size); if (ext === '.mp4') { // MP4 需要先本地提取音频 tempVideoPath = await join('temp', `upload_${Date.now()}_${Math.random().toString(36).slice(2, 8)}.mp4`); + console.log('[VoiceStore] tempVideoPath=', tempVideoPath); // 1a. 将 File 写入本地临时文件(使用 BaseDirectory.AppLocalData) + console.log('[VoiceStore] 开始 writeFile...'); + await mkdir('temp', { baseDir: BaseDirectory.AppLocalData, recursive: true }); const arrayBuffer = await file.arrayBuffer(); const uint8Array = new Uint8Array(arrayBuffer); await writeFile(tempVideoPath, uint8Array, { baseDir: BaseDirectory.AppLocalData }); + console.log('[VoiceStore] writeFile 完成'); // 1b. 调用 FFmpeg 提取音频(Rust 返回绝对路径) - tempMp3Path = await voiceApi.extractAudioFromVideo( - await join(await appLocalDataDir(), tempVideoPath) - ); + const videoAbsPath = await join(await appLocalDataDir(), tempVideoPath); + console.log('[VoiceStore] 开始 extractAudioFromVideo, path=', videoAbsPath); + tempMp3Path = await voiceApi.extractAudioFromVideo(videoAbsPath); + console.log('[VoiceStore] extractAudioFromVideo 完成, mp3Path=', tempMp3Path); // 1c. 上传提取的 MP3 + console.log('[VoiceStore] 开始 uploadLocalAudioFile...'); sourceUrl = await voiceApi.uploadLocalAudioFile(tempMp3Path); + console.log('[VoiceStore] uploadLocalAudioFile 完成, url=', sourceUrl); } else { // 音频文件直接上传 + console.log('[VoiceStore] 直接上传音频文件...'); sourceUrl = await voiceApi.uploadAudio(file); + console.log('[VoiceStore] uploadAudio 完成, url=', sourceUrl); } // 2. 提交 Vidu 同步克隆 + console.log('[VoiceStore] 开始 submitCloneTask...'); const cloneResult = await voiceApi.submitCloneTask({ sourceAudioUrl: sourceUrl, voiceName: name, }); + console.log('[VoiceStore] submitCloneTask 完成, result=', cloneResult); // 3. Vidu 同步返回,直接 ready const material: VoiceMaterial = { @@ -174,26 +186,36 @@ export const useVoiceStore = create()( status: 'ready', createdAt: new Date().toISOString(), }; + console.log('[VoiceStore] material=', material); // 4. 保存到本地 JSON + console.log('[VoiceStore] 开始 saveVoiceMaterial...'); await voiceApi.saveVoiceMaterial(material); + console.log('[VoiceStore] saveVoiceMaterial 完成'); set(state => ({ voiceMaterials: [material, ...state.voiceMaterials] })); + console.log('[VoiceStore] addVoiceMaterial 成功返回'); return material; + } catch (err) { + console.error('[VoiceStore] addVoiceMaterial 异常:', err, '类型:', typeof err, '是Error?', err instanceof Error, 'message:', (err as Error)?.message); + throw err; } finally { // 清理临时文件 + console.log('[VoiceStore] finally 清理, tempVideoPath=', tempVideoPath, 'tempMp3Path=', tempMp3Path); if (tempVideoPath) { try { await remove(tempVideoPath, { baseDir: BaseDirectory.AppLocalData }); - } catch { - // 忽略清理错误 + console.log('[VoiceStore] 清理 tempVideoPath 成功'); + } catch (e) { + console.error('[VoiceStore] 清理 tempVideoPath 失败:', e); } } if (tempMp3Path) { try { - await remove(tempMp3Path); - } catch { - // 忽略清理错误 + await remove(tempMp3Path, { baseDir: BaseDirectory.AppLocalData }); + console.log('[VoiceStore] 清理 tempMp3Path 成功'); + } catch (e) { + console.error('[VoiceStore] 清理 tempMp3Path 失败:', e); } } }