/** * 音效系统 - 后端 IPC 处理器 */ import { ipcMain } from 'electron'; import { v4 as uuidv4 } from 'uuid'; import * as path from 'path'; import * as fs from 'fs'; import * as os from 'os'; import StorageMain from '../storage/main'; import { SoundEffectDb } from '../db/soundEffectDb'; import { getAllBuiltinEffects, getBuiltinEffect, generateFFmpegCommand, getBuiltinEffectFilePath } from './builtinEffects'; import shellApi from '../shell/index'; let db: any = null; let soundEffectDb: SoundEffectDb | null = null; /** * 初始化数据库连接 */ export function initSoundEffectDb(dbInstance: any) { db = dbInstance; soundEffectDb = new SoundEffectDb(db); } /** * 初始化系统内置音效 */ export async function initBuiltinSoundEffects() { if (!soundEffectDb) { console.warn('[SoundEffect] 数据库未初始化,跳过内置音效初始化'); return; } try { console.log('[SoundEffect] 开始初始化系统内置音效...'); // 先清除旧的系统音效数据,确保同步最新的音效列表 await soundEffectDb.clearBuiltinEffects(); console.log('[SoundEffect] 已清除旧的系统音效数据'); const effects = getAllBuiltinEffects(); for (const effect of effects) { try { const record = { id: effect.id as string, name: effect.name, description: effect.description, category: effect.category, type: effect.id, audio_source: null, // 使用预置音效文件,不需要合成配置 volume: effect.defaultParams.volume || 0.7, duration: 0.5, // 默认时长 metadata: JSON.stringify(effect.defaultParams), created_at: Date.now(), updated_at: Date.now(), is_system: 1 }; await soundEffectDb.saveBuiltinEffect(record); console.log(`[SoundEffect] ✅ 初始化音效: ${effect.name}`); } catch (error) { console.warn(`[SoundEffect] ⚠️ 初始化音效失败: ${effect.name}`, error); } } console.log(`[SoundEffect] ✅ 系统内置音效初始化完成,共 ${effects.length} 个音效`); } catch (error) { console.error('[SoundEffect] ❌ 初始化音效系统失败:', error); } } // ==================== IPC 事件处理 ==================== /** * 获取所有内置音效 */ ipcMain.handle('soundEffect:getBuiltinEffects', async (event) => { try { if (!soundEffectDb) { // 如果数据库未初始化,从库中获取 return getAllBuiltinEffects(); } const effects = await soundEffectDb.getBuiltinEffects(); return effects.map((e) => ({ id: e.id, name: e.name, description: e.description, category: e.category, defaultParams: e.metadata ? JSON.parse(e.metadata) : {} })); } catch (error) { console.error('[SoundEffect] 获取内置音效失败:', error); return getAllBuiltinEffects(); } }); /** * 🆕 获取所有音效(内置 + 用户自定义) * 用户音效会合并到列表中,并标记为自定义 */ ipcMain.handle('soundEffect:getAllEffects', async (event) => { try { // 1. 获取内置音效 let builtinEffects: any[] = []; if (soundEffectDb) { const effects = await soundEffectDb.getBuiltinEffects(); builtinEffects = effects.map((e) => ({ id: e.id, name: e.name, description: e.description, category: e.category, isCustom: false, defaultParams: e.metadata ? JSON.parse(e.metadata) : {} })); } else { builtinEffects = getAllBuiltinEffects().map(e => ({ ...e, isCustom: false })); } // 2. 获取用户自定义音效 let userEffects: any[] = []; if (soundEffectDb) { const effects = await soundEffectDb.getUserEffects(); userEffects = effects.map((e) => ({ id: e.id, // 已经是 user_xxx 格式 name: `🎵 ${e.name}`, // 添加标识 description: e.description || '自定义音效', category: 'custom', isCustom: true, filePath: e.file_path, fileSize: e.file_size })); } // 3. 合并列表:内置在前,用户在后 const allEffects = [...builtinEffects, ...userEffects]; console.log(`[SoundEffect] 获取所有音效: ${builtinEffects.length} 个内置 + ${userEffects.length} 个自定义`); return allEffects; } catch (error) { console.error('[SoundEffect] 获取所有音效失败:', error); return getAllBuiltinEffects().map(e => ({ ...e, isCustom: false })); } }); /** * 获取所有用户自定义音效 */ ipcMain.handle('soundEffect:getUserEffects', async (event) => { try { if (!soundEffectDb) { return []; } const effects = await soundEffectDb.getUserEffects(); return effects.map((e) => ({ id: e.id, name: e.name, description: e.description, filePath: e.file_path, duration: e.duration, fileSize: e.file_size, createdAt: e.created_at, category: 'custom' })); } catch (error) { console.error('[SoundEffect] 获取用户音效失败:', error); return []; } }); /** * 导入用户音效文件 */ ipcMain.handle( 'soundEffect:importUserEffect', async (event, { filePath, name, description }) => { console.log(`[SoundEffect] 📥 开始导入用户音效:`, { filePath, name, description }); try { if (!soundEffectDb) { console.error('[SoundEffect] ❌ 数据库未初始化'); return { success: false, message: '数据库未初始化' }; } // 检查文件是否存在 if (!fs.existsSync(filePath)) { console.error(`[SoundEffect] ❌ 音效文件不存在: ${filePath}`); return { success: false, message: '音效文件不存在' }; } // 检查名称是否已存在 const nameExists = await soundEffectDb.isNameExists(name); if (nameExists) { console.warn(`[SoundEffect] ⚠️ 音效名称已存在: ${name}`); return { success: false, message: '音效名称已存在' }; } // 创建用户音效目录 const userSoundsDir = StorageMain.getUserSoundsDir(); console.log(`[SoundEffect] 📁 用户音效目录: ${userSoundsDir}`); if (!fs.existsSync(userSoundsDir)) { fs.mkdirSync(userSoundsDir, { recursive: true }); console.log(`[SoundEffect] ✅ 创建用户音效目录成功`); } // 复制文件到用户目录 // 使用 user_ 前缀区分用户音效和内置音效 const effectId = `user_${uuidv4()}`; const fileExtension = path.extname(filePath); const destPath = path.join(userSoundsDir, `${effectId}${fileExtension}`); console.log(`[SoundEffect] 📋 复制文件: ${filePath} -> ${destPath}`); fs.copyFileSync(filePath, destPath); console.log(`[SoundEffect] ✅ 文件复制成功`); // 保存到数据库 const record = { id: effectId, name, description: description || null, file_path: destPath, duration: null, file_size: fs.statSync(destPath).size, created_at: Date.now(), updated_at: Date.now() }; console.log(`[SoundEffect] 💾 保存到数据库:`, record); await soundEffectDb.saveUserEffect(record); console.log(`[SoundEffect] ✅ 导入用户音效成功: ${name} (ID: ${effectId})`); return { success: true, id: effectId, message: '音效导入成功' }; } catch (error) { console.error('[SoundEffect] ❌ 导入用户音效失败:', error); return { success: false, message: `导入失败: ${error}` }; } } ); /** * 删除用户自定义音效 */ ipcMain.handle('soundEffect:deleteUserEffect', async (event, effectId) => { try { if (!soundEffectDb) { return { success: false, message: '数据库未初始化' }; } const effect = await soundEffectDb.getUserEffectById(effectId); if (!effect) { return { success: false, message: '音效不存在' }; } // 删除文件 if (fs.existsSync(effect.file_path)) { try { fs.unlinkSync(effect.file_path); } catch (error) { console.warn('[SoundEffect] 删除音效文件失败:', error); } } // 删除数据库记录 await soundEffectDb.deleteUserEffect(effectId); console.log(`[SoundEffect] ✅ 删除用户音效: ${effectId}`); return { success: true, message: '音效已删除' }; } catch (error) { console.error('[SoundEffect] 删除用户音效失败:', error); return { success: false, message: `删除失败: ${error}` }; } }); /** * 生成音效文件(使用 FFmpeg 合成) */ ipcMain.handle( 'soundEffect:generateEffect', async (event, { effectId, params, outputPath }) => { try { // 检查是否是内置音效 const effect = getBuiltinEffect(effectId); if (!effect) { return { success: false, error: `未找到音效: ${effectId}` }; } // 生成 FFmpeg 命令 const command = generateFFmpegCommand( effectId, params, outputPath ); console.log(`[SoundEffect] 生成音效命令:`, command); // 这里需要调用 FFmpeg 执行命令 // 暂时返回命令,实际执行由调用方负责 return { success: true, ffmpegCommand: command, audioFile: outputPath }; } catch (error) { console.error('[SoundEffect] 生成音效失败:', error); return { success: false, error: `生成失败: ${error}` }; } } ); /** * 预览音效 * 统一处理:根据 ID 前缀自动判断是用户音效 (user_*) 还是内置音效 */ ipcMain.handle( 'soundEffect:previewEffect', async (event, { effectId, userEffectId }) => { try { // 统一使用 effectId,兼容旧的 userEffectId 参数 const id = effectId || userEffectId; if (!id) { return { success: false, message: '需要提供 effectId' }; } console.log(`[SoundEffect] 🎵 预览音效: ${id}`); // 根据 ID 前缀判断类型 if (id.startsWith('user_')) { // 用户自定义音效 if (!soundEffectDb) { return { success: false, message: '数据库未初始化' }; } const effect = await soundEffectDb.getUserEffectById(id); if (!effect) { console.error(`[SoundEffect] ❌ 用户音效不存在: ${id}`); return { success: false, message: '音效不存在' }; } if (!effect.file_path || !fs.existsSync(effect.file_path)) { console.error(`[SoundEffect] ❌ 音效文件不存在: ${effect.file_path}`); return { success: false, message: '音效文件不存在' }; } console.log(`[SoundEffect] ✅ 预览用户音效: ${effect.file_path}`); return { success: true, audioPath: effect.file_path }; } else { // 内置音效 - 优先使用预置音效文件 const effectFilePath = getBuiltinEffectFilePath(id); if (effectFilePath) { console.log(`[SoundEffect] ✅ 预览内置音效: ${effectFilePath}`); return { success: true, audioPath: effectFilePath }; } // 降级到 FFmpeg 合成(当预置文件不存在时) console.log('[SoundEffect] 预置音效不存在,降级到 FFmpeg 合成'); return { success: false, message: `内置音效文件不存在或映射错误: ${id}` }; const tempDir = os.tmpdir(); const tempPath = path.join( tempDir, `preview_${Date.now()}.wav` ); const command = generateFFmpegCommand( id, {}, tempPath ); console.log('[SoundEffect] 预览音效命令:', command); try { // 解析命令字符串为参数数组 const args = command .replace(/^ffmpeg\s+/, '') // 移除 'ffmpeg' 前缀 .match(/(?:[^\s"]+|"[^"]*")+/g) // 分割参数,保留引号内的空格 .map(arg => arg.replace(/^"|"$/g, '')); // 移除参数两端的引号 console.log('[SoundEffect] FFmpeg 参数:', args); // 执行 FFmpeg 命令 await shellApi.execFFmpegCommand(args); console.log('[SoundEffect] 音效文件生成成功:', tempPath); // 返回生成的音频文件路径 return { success: true, audioPath: tempPath }; } catch (error) { console.error('[SoundEffect] FFmpeg 执行失败:', error); return { success: false, message: `音效生成失败: ${error}` }; } } } catch (error) { console.error('[SoundEffect] 预览音效失败:', error); return { success: false, message: `预览失败: ${error}` }; } } ); /** * 🆕 获取音效文件路径(用于视频生成时的音效混音) * 统一处理:根据 ID 前缀自动判断是用户音效 (user_*) 还是内置音效 */ ipcMain.handle( 'soundEffect:getFilePath', async (event, { effectId, userEffectId }) => { try { // 统一使用 effectId,兼容旧的 userEffectId 参数 const id = effectId || userEffectId; if (!id) { return { success: false, message: '需要提供 effectId' }; } // 根据 ID 前缀判断类型 if (id.startsWith('user_')) { // 用户自定义音效 if (!soundEffectDb) { return { success: false, message: '数据库未初始化' }; } const effect = await soundEffectDb.getUserEffectById(id); if (effect && effect.file_path && fs.existsSync(effect.file_path)) { console.log(`[SoundEffect] ✅ 获取用户音效路径: ${id} -> ${effect.file_path}`); return { success: true, filePath: effect.file_path }; } return { success: false, message: `用户音效文件不存在: ${id}` }; } else { // 内置音效 const filePath = getBuiltinEffectFilePath(id); if (filePath) { console.log(`[SoundEffect] ✅ 获取内置音效路径: ${id} -> ${filePath}`); return { success: true, filePath: filePath }; } return { success: false, message: `内置音效文件不存在: ${id}` }; } } catch (error) { console.error('[SoundEffect] 获取音效文件路径失败:', error); return { success: false, message: `获取失败: ${error}` }; } } ); /** * 🆕 批量获取音效文件路径(用于视频生成时的批量处理) */ ipcMain.handle( 'soundEffect:getBatchFilePaths', async (event, { soundInstances }) => { try { const results: { effectId: string; filePath: string | null; triggerTime: number }[] = []; for (const instance of soundInstances) { const effectId = instance.effectId; const filePath = getBuiltinEffectFilePath(effectId); if (!filePath) { return { success: false, message: `内置音效文件不存在或映射错误: ${effectId}` }; } results.push({ effectId, filePath, triggerTime: instance.triggerTime }); if (filePath) { console.log(`[SoundEffect] 获取音效路径: ${effectId} -> ${filePath}`); } else { console.warn(`[SoundEffect] 音效文件不存在: ${effectId}`); } } return { success: true, results }; } catch (error) { console.error('[SoundEffect] 批量获取音效路径失败:', error); return { success: false, message: `获取失败: ${error}` }; } } );