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:
fritzsenpai 2025-09-25 21:19:58 -05:00
parent 5caa82e659
commit c67d4dca27
2 changed files with 301 additions and 0 deletions

View File

@ -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
View File

@ -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;
}
});