Implement Phase 1: Desktop Audio Management Infrastructure
- Add comprehensive audio file management to desktop-file-manager.js - Audio directory structure (background/ambient/effects categories) - selectAndImportAudio() with category selection and file dialog integration - scanDirectoryForAudio() with category-specific scanning - updateAudioStorage() with duplicate prevention and storage management - deleteAudio() with file cleanup and storage updates - Utility functions: getAudioTitle() and getAudioPath() - Add audio-specific IPC handlers to main.js - select-audio: Multi-selection file dialog for audio files - read-audio-directory: Directory scanning with title formatting - copy-audio: File copying with directory creation - Support for MP3, WAV, OGG, M4A, AAC, FLAC formats - Foundation ready for Phase 2: Audio Management UI implementation
This commit is contained in:
parent
5caa82e659
commit
c67d4dca27
|
|
@ -7,6 +7,11 @@ class DesktopFileManager {
|
|||
tasks: null,
|
||||
consequences: null
|
||||
};
|
||||
this.audioDirectories = {
|
||||
background: null,
|
||||
ambient: null,
|
||||
effects: null
|
||||
};
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
|
@ -22,13 +27,22 @@ class DesktopFileManager {
|
|||
this.imageDirectories.tasks = await window.electronAPI.pathJoin(this.appPath, 'images', 'tasks');
|
||||
this.imageDirectories.consequences = await window.electronAPI.pathJoin(this.appPath, 'images', 'consequences');
|
||||
|
||||
this.audioDirectories.background = await window.electronAPI.pathJoin(this.appPath, 'audio', 'background');
|
||||
this.audioDirectories.ambient = await window.electronAPI.pathJoin(this.appPath, 'audio', 'ambient');
|
||||
this.audioDirectories.effects = await window.electronAPI.pathJoin(this.appPath, 'audio', 'effects');
|
||||
|
||||
// Ensure directories exist
|
||||
await window.electronAPI.createDirectory(this.imageDirectories.tasks);
|
||||
await window.electronAPI.createDirectory(this.imageDirectories.consequences);
|
||||
|
||||
await window.electronAPI.createDirectory(this.audioDirectories.background);
|
||||
await window.electronAPI.createDirectory(this.audioDirectories.ambient);
|
||||
await window.electronAPI.createDirectory(this.audioDirectories.effects);
|
||||
|
||||
console.log('Desktop file manager initialized');
|
||||
console.log('App path:', this.appPath);
|
||||
console.log('Image directories:', this.imageDirectories);
|
||||
console.log('Audio directories:', this.audioDirectories);
|
||||
} else {
|
||||
console.error('Failed to get app path');
|
||||
}
|
||||
|
|
@ -98,6 +112,78 @@ class DesktopFileManager {
|
|||
}
|
||||
}
|
||||
|
||||
async selectAndImportAudio(category = 'background') {
|
||||
if (!this.isElectron) {
|
||||
this.showNotification('Audio import only available in desktop version', 'warning');
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
// Open file dialog for audio files
|
||||
const filePaths = await window.electronAPI.selectAudio();
|
||||
|
||||
if (filePaths.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const importedAudio = [];
|
||||
let targetDir;
|
||||
|
||||
switch(category) {
|
||||
case 'background':
|
||||
targetDir = this.audioDirectories.background;
|
||||
break;
|
||||
case 'ambient':
|
||||
targetDir = this.audioDirectories.ambient;
|
||||
break;
|
||||
case 'effects':
|
||||
targetDir = this.audioDirectories.effects;
|
||||
break;
|
||||
default:
|
||||
targetDir = this.audioDirectories.background;
|
||||
}
|
||||
|
||||
if (!targetDir) {
|
||||
console.error('Target audio directory not initialized');
|
||||
this.showNotification('Audio directory not initialized', 'error');
|
||||
return [];
|
||||
}
|
||||
|
||||
for (const filePath of filePaths) {
|
||||
const fileName = filePath.split(/[\\/]/).pop();
|
||||
const targetPath = await window.electronAPI.pathJoin(targetDir, fileName);
|
||||
|
||||
// Copy file to app directory
|
||||
const success = await window.electronAPI.copyAudio(filePath, targetPath);
|
||||
|
||||
if (success) {
|
||||
importedAudio.push({
|
||||
name: fileName,
|
||||
path: targetPath,
|
||||
category: category,
|
||||
title: this.getAudioTitle(fileName)
|
||||
});
|
||||
console.log(`Imported audio: ${fileName} to ${category}`);
|
||||
} else {
|
||||
console.error(`Failed to import audio: ${fileName}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (importedAudio.length > 0) {
|
||||
// Update the game's audio storage
|
||||
await this.updateAudioStorage(importedAudio);
|
||||
this.showNotification(`Imported ${importedAudio.length} audio file(s) to ${category}!`, 'success');
|
||||
}
|
||||
|
||||
return importedAudio;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error importing audio:', error);
|
||||
this.showNotification('Failed to import audio files', 'error');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async scanDirectoryForImages(category = 'task') {
|
||||
if (!this.isElectron) {
|
||||
return [];
|
||||
|
|
@ -244,6 +330,176 @@ class DesktopFileManager {
|
|||
console.log(`${category} images directory:`, dir);
|
||||
}
|
||||
|
||||
async scanDirectoryForAudio(category = 'background') {
|
||||
if (!this.isElectron) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
let targetDir;
|
||||
|
||||
switch(category) {
|
||||
case 'background':
|
||||
targetDir = this.audioDirectories.background;
|
||||
break;
|
||||
case 'ambient':
|
||||
targetDir = this.audioDirectories.ambient;
|
||||
break;
|
||||
case 'effects':
|
||||
targetDir = this.audioDirectories.effects;
|
||||
break;
|
||||
default:
|
||||
targetDir = this.audioDirectories.background;
|
||||
}
|
||||
|
||||
if (!targetDir) {
|
||||
console.error(`Target audio directory not initialized for ${category}`);
|
||||
return [];
|
||||
}
|
||||
|
||||
const audioFiles = await window.electronAPI.readAudioDirectory(targetDir);
|
||||
|
||||
console.log(`Found ${audioFiles.length} audio files in ${category} directory`);
|
||||
return audioFiles.map(audio => ({
|
||||
...audio,
|
||||
category: category,
|
||||
title: this.getAudioTitle(audio.name)
|
||||
}));
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Error scanning ${category} audio directory:`, error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async scanAllAudioDirectories() {
|
||||
if (!this.isElectron) {
|
||||
this.showNotification('Audio directory scanning only available in desktop version', 'warning');
|
||||
return { background: [], ambient: [], effects: [] };
|
||||
}
|
||||
|
||||
const backgroundAudio = await this.scanDirectoryForAudio('background');
|
||||
const ambientAudio = await this.scanDirectoryForAudio('ambient');
|
||||
const effectsAudio = await this.scanDirectoryForAudio('effects');
|
||||
|
||||
const results = {
|
||||
background: backgroundAudio,
|
||||
ambient: ambientAudio,
|
||||
effects: effectsAudio
|
||||
};
|
||||
|
||||
// Update game audio storage
|
||||
if (backgroundAudio.length > 0 || ambientAudio.length > 0 || effectsAudio.length > 0) {
|
||||
await this.updateAudioStorage([...backgroundAudio, ...ambientAudio, ...effectsAudio]);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
async updateAudioStorage(audioFiles) {
|
||||
if (!Array.isArray(audioFiles) || audioFiles.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get existing audio
|
||||
let customAudio = this.dataManager.get('customAudio') || { background: [], ambient: [], effects: [] };
|
||||
|
||||
// Add new audio files (avoid duplicates)
|
||||
for (const audio of audioFiles) {
|
||||
const category = audio.category || 'background';
|
||||
|
||||
if (!customAudio[category]) {
|
||||
customAudio[category] = [];
|
||||
}
|
||||
|
||||
// Check for duplicates by path
|
||||
const exists = customAudio[category].some(existing => {
|
||||
if (typeof existing === 'string') {
|
||||
return existing === audio.path;
|
||||
} else if (typeof existing === 'object') {
|
||||
return existing.path === audio.path;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (!exists) {
|
||||
customAudio[category].push({
|
||||
name: audio.name,
|
||||
path: audio.path,
|
||||
title: audio.title,
|
||||
category: audio.category
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Save back to storage
|
||||
this.dataManager.set('customAudio', customAudio);
|
||||
console.log('Audio storage updated:', customAudio);
|
||||
}
|
||||
|
||||
async deleteAudio(audioPath, category = 'background') {
|
||||
if (!this.isElectron) {
|
||||
this.showNotification('Audio deletion only available in desktop version', 'warning');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const success = await window.electronAPI.deleteFile(audioPath);
|
||||
if (success) {
|
||||
// Remove from storage
|
||||
let customAudio = this.dataManager.get('customAudio') || { background: [], ambient: [], effects: [] };
|
||||
|
||||
if (customAudio[category]) {
|
||||
customAudio[category] = customAudio[category].filter(audio => {
|
||||
if (typeof audio === 'string') {
|
||||
return audio !== audioPath;
|
||||
}
|
||||
return audio.path !== audioPath;
|
||||
});
|
||||
}
|
||||
|
||||
this.dataManager.set('customAudio', customAudio);
|
||||
console.log(`Deleted audio file: ${audioPath}`);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.error('Error deleting audio:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
getAudioTitle(fileName) {
|
||||
// Remove file extension and clean up the name for display
|
||||
return fileName
|
||||
.replace(/\.[^/.]+$/, '') // Remove extension
|
||||
.replace(/[-_]/g, ' ') // Replace dashes and underscores with spaces
|
||||
.replace(/\b\w/g, l => l.toUpperCase()); // Capitalize first letters
|
||||
}
|
||||
|
||||
getAudioPath(audioName, category = 'background') {
|
||||
if (!this.isElectron) {
|
||||
return `audio/${audioName}`;
|
||||
}
|
||||
|
||||
let dir;
|
||||
switch(category) {
|
||||
case 'background':
|
||||
dir = this.audioDirectories.background;
|
||||
break;
|
||||
case 'ambient':
|
||||
dir = this.audioDirectories.ambient;
|
||||
break;
|
||||
case 'effects':
|
||||
dir = this.audioDirectories.effects;
|
||||
break;
|
||||
default:
|
||||
dir = this.audioDirectories.background;
|
||||
}
|
||||
|
||||
return `${dir}/${audioName}`;
|
||||
}
|
||||
|
||||
showNotification(message, type = 'info') {
|
||||
// Use the game's existing notification system
|
||||
if (window.game && window.game.showNotification) {
|
||||
|
|
|
|||
45
main.js
45
main.js
|
|
@ -136,4 +136,49 @@ ipcMain.handle('delete-file', async (event, filePath) => {
|
|||
console.error('Error deleting file:', error);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// Audio-specific IPC handlers
|
||||
ipcMain.handle('select-audio', async () => {
|
||||
const result = await dialog.showOpenDialog(mainWindow, {
|
||||
properties: ['openFile', 'multiSelections'],
|
||||
filters: [
|
||||
{ name: 'Audio Files', extensions: ['mp3', 'wav', 'ogg', 'm4a', 'aac', 'flac'] }
|
||||
]
|
||||
});
|
||||
|
||||
if (!result.canceled) {
|
||||
return result.filePaths;
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
ipcMain.handle('read-audio-directory', async (event, dirPath) => {
|
||||
try {
|
||||
const files = await fs.readdir(dirPath);
|
||||
const audioFiles = files.filter(file => {
|
||||
const ext = path.extname(file).toLowerCase();
|
||||
return ['.mp3', '.wav', '.ogg', '.m4a', '.aac', '.flac'].includes(ext);
|
||||
});
|
||||
|
||||
return audioFiles.map(file => ({
|
||||
name: file,
|
||||
path: path.join(dirPath, file),
|
||||
title: file.replace(/\.[^/.]+$/, "").replace(/[-_]/g, ' ').replace(/\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase())
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('Error reading audio directory:', error);
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('copy-audio', async (event, sourcePath, destPath) => {
|
||||
try {
|
||||
await fs.mkdir(path.dirname(destPath), { recursive: true });
|
||||
await fs.copyFile(sourcePath, destPath);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Error copying audio file:', error);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
Loading…
Reference in New Issue