start of react migration
This commit is contained in:
483
electron-ui/src/old/main.js
Normal file
483
electron-ui/src/old/main.js
Normal file
@ -0,0 +1,483 @@
|
||||
const { app, BrowserWindow, ipcMain, Tray, Menu, dialog } = require('electron');
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
const spawn = require('child_process').spawn;
|
||||
require('electron-reload')(__dirname);
|
||||
const fs = require('fs').promises;
|
||||
const chokidar = require('chokidar');
|
||||
const wavefile = require('wavefile');
|
||||
const MetadataManager = require('./metatadata');
|
||||
|
||||
const { webContents } = require('electron');
|
||||
|
||||
// import { app, BrowserWindow, ipcMain, Tray, Menu, dialog } from "electron";
|
||||
// import path from "path";
|
||||
// import os from "os";
|
||||
// import spawn from 'child_process';
|
||||
// import fs from "fs";
|
||||
// import chokidar from "chokidar";
|
||||
// import wavefile from "wavefile";
|
||||
// import MetadataManager from "./metatadata.cjs";
|
||||
// import { webContents } from "electron";
|
||||
|
||||
let mainWindow;
|
||||
let tray;
|
||||
let audioServiceProcess;
|
||||
|
||||
const metadataPath = path.join(app.getPath('userData'), 'audio_metadata.json');
|
||||
const metadataManager = new MetadataManager(metadataPath);
|
||||
|
||||
async function createPythonService() {
|
||||
const pythonPath =
|
||||
process.platform === 'win32'
|
||||
? path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
'..',
|
||||
'audio-service',
|
||||
'venv',
|
||||
'Scripts',
|
||||
'python.exe',
|
||||
)
|
||||
: path.join(__dirname, '..', 'audio-service', 'venv', 'bin', 'python');
|
||||
|
||||
const scriptPath = path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
'..',
|
||||
'audio-service',
|
||||
'src',
|
||||
'main.py',
|
||||
);
|
||||
|
||||
// Load settings to pass as arguments
|
||||
const settings = await loadSettings(); // Assuming you have a method to load settings
|
||||
|
||||
const args = [
|
||||
scriptPath,
|
||||
'--recording-length',
|
||||
settings.recordingLength.toString(),
|
||||
'--save-path',
|
||||
path.join(settings.outputFolder, 'original'),
|
||||
'--osc-port',
|
||||
settings.oscPort.toString(), // Or make this configurable
|
||||
];
|
||||
|
||||
// Add input device if specified
|
||||
if (settings.inputDevice) {
|
||||
const devices = await listAudioDevices();
|
||||
args.push(
|
||||
'--input-device',
|
||||
devices.find((device) => device.id === settings.inputDevice)?.name,
|
||||
);
|
||||
}
|
||||
|
||||
console.log(args);
|
||||
|
||||
audioServiceProcess = spawn(pythonPath, args, {
|
||||
detached: false,
|
||||
stdio: 'pipe',
|
||||
});
|
||||
|
||||
audioServiceProcess.stdout.on('data', (data) => {
|
||||
console.log(`Audio Service: ${data}`);
|
||||
});
|
||||
|
||||
audioServiceProcess.stderr.on('data', (data) => {
|
||||
console.error(`Audio Service Error: ${data}`);
|
||||
});
|
||||
|
||||
audioServiceProcess.on('close', (code) => {
|
||||
console.log(`Audio Service process exited with code ${code}`);
|
||||
audioServiceProcess = null;
|
||||
});
|
||||
}
|
||||
|
||||
function createTray() {
|
||||
tray = new Tray(path.join(__dirname, 'assets', 'tray-icon.png')); // You'll need to create this icon
|
||||
|
||||
const contextMenu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: 'Show',
|
||||
click: () => {
|
||||
mainWindow.show();
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Quit',
|
||||
click: () => {
|
||||
// Properly terminate the Python service
|
||||
|
||||
stopService();
|
||||
app.quit();
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
tray.setToolTip('Audio Trimmer');
|
||||
tray.setContextMenu(contextMenu);
|
||||
}
|
||||
|
||||
async function checkNewWavFile(filePath) {
|
||||
// Only process .wav files
|
||||
if (path.extname(filePath).toLowerCase() === '.wav') {
|
||||
try {
|
||||
await metadataManager.addUntrimmedFile(filePath);
|
||||
|
||||
// Notify renderer if window is ready
|
||||
if (mainWindow) {
|
||||
mainWindow.webContents.send('new-untrimmed-file', filePath);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error adding untrimmed file:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function stopService() {
|
||||
if (audioServiceProcess) {
|
||||
try {
|
||||
if (process.platform === 'win32') {
|
||||
spawn('taskkill', ['/pid', audioServiceProcess.pid, '/f', '/t']);
|
||||
} else {
|
||||
audioServiceProcess.kill('SIGTERM');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error killing audio service:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function restartService() {
|
||||
// Properly terminate the Python service
|
||||
stopService();
|
||||
//delay for 2 seconds
|
||||
setTimeout(createPythonService, 4000);
|
||||
//createPythonService();
|
||||
}
|
||||
|
||||
async function loadSettings() {
|
||||
try {
|
||||
const settingsPath = path.join(app.getPath('userData'), 'settings.json');
|
||||
const settingsData = await fs.readFile(settingsPath, 'utf8');
|
||||
return JSON.parse(settingsData);
|
||||
} catch (error) {
|
||||
// If no settings file exists, return default settings
|
||||
return {
|
||||
recordingLength: 30,
|
||||
outputFolder: path.join(os.homedir(), 'AudioTrimmer'),
|
||||
inputDevice: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function listAudioDevices() {
|
||||
try {
|
||||
// Use a webContents to access navigator.mediaDevices
|
||||
|
||||
const contents = webContents.getAllWebContents()[0];
|
||||
|
||||
const devices = await contents.executeJavaScript(`
|
||||
navigator.mediaDevices.enumerateDevices()
|
||||
.then(devices => devices.filter(device => device.kind === 'audioinput'))
|
||||
.then(audioDevices => audioDevices.map(device => ({
|
||||
id: device.deviceId,
|
||||
name: device.label || 'Unknown Microphone'
|
||||
})))
|
||||
`);
|
||||
|
||||
return devices;
|
||||
} catch (error) {
|
||||
console.error('Error getting input devices:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
async function createWindow() {
|
||||
// Initialize metadata
|
||||
await metadataManager.initialize();
|
||||
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1200,
|
||||
height: 800,
|
||||
autoHideMenuBar: true,
|
||||
titleBarStyle: 'hidden',
|
||||
frame: false,
|
||||
|
||||
// titleBarOverlay: {
|
||||
// color: '#1e1e1e',
|
||||
// symbolColor: '#ffffff',
|
||||
// height: 30
|
||||
// },
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false,
|
||||
// Add these to help with graphics issues
|
||||
},
|
||||
// These additional options can help with graphics rendering
|
||||
backgroundColor: '#1e1e1e',
|
||||
...(process.platform !== 'darwin'
|
||||
? {
|
||||
titleBarOverlay: {
|
||||
color: '#262626',
|
||||
symbolColor: '#ffffff',
|
||||
height: 30,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
});
|
||||
mainWindow.loadFile('src/index.html');
|
||||
|
||||
// Create Python ser
|
||||
const settings = await loadSettings(); // Assuming you have a method to load settings
|
||||
const recordingsPath = path.join(settings.outputFolder, 'original');
|
||||
// Ensure recordings directory exists
|
||||
try {
|
||||
await fs.mkdir(recordingsPath, { recursive: true });
|
||||
} catch (error) {
|
||||
console.error('Error creating recordings directory:', error);
|
||||
}
|
||||
|
||||
// Watch for new WAV files
|
||||
const watcher = chokidar.watch(recordingsPath, {
|
||||
ignored: /(^|[\/\\])\../, // ignore dotfiles
|
||||
persistent: true,
|
||||
depth: 0,
|
||||
awaitWriteFinish: {
|
||||
stabilityThreshold: 2000,
|
||||
pollInterval: 100,
|
||||
},
|
||||
});
|
||||
|
||||
fs.readdir(recordingsPath).then((files) => {
|
||||
files.forEach((file) => {
|
||||
checkNewWavFile(path.join(recordingsPath, file));
|
||||
});
|
||||
});
|
||||
|
||||
watcher.on('add', async (filePath) => {
|
||||
await checkNewWavFile(filePath);
|
||||
});
|
||||
|
||||
ipcMain.handle('get-collections', () => {
|
||||
return metadataManager.getCollections();
|
||||
});
|
||||
|
||||
ipcMain.handle('get-collection-files', (event, collectionPath) => {
|
||||
return metadataManager.getFilesInCollection(collectionPath);
|
||||
});
|
||||
|
||||
ipcMain.handle('add-untrimmed-file', (event, filePath) => {
|
||||
return metadataManager.addUntrimmedFile(filePath);
|
||||
});
|
||||
|
||||
ipcMain.handle(
|
||||
'save-trimmed-file',
|
||||
(event, fileName, previousPath, savePath, trimStart, trimEnd, title) => {
|
||||
return metadataManager.saveTrimmedFile(
|
||||
fileName,
|
||||
previousPath,
|
||||
savePath,
|
||||
trimStart,
|
||||
trimEnd,
|
||||
title,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
ipcMain.handle('restart', (event) => {
|
||||
restartService();
|
||||
});
|
||||
|
||||
ipcMain.handle('delete-old-file', (event, outputFolder, section, title) => {
|
||||
if (section === 'untrimmed') return;
|
||||
const collectionPath = path.join(outputFolder, section);
|
||||
const outputFilePath = path.join(collectionPath, `${title}.wav`);
|
||||
fs.unlink(outputFilePath);
|
||||
});
|
||||
ipcMain.handle(
|
||||
'save-trimmed-audio',
|
||||
async (
|
||||
event,
|
||||
{
|
||||
originalFilePath,
|
||||
outputFolder,
|
||||
collectionName,
|
||||
title,
|
||||
trimStart,
|
||||
trimEnd,
|
||||
},
|
||||
) => {
|
||||
try {
|
||||
// Ensure the collection folder exists
|
||||
const collectionPath = path.join(outputFolder, collectionName);
|
||||
await fs.mkdir(collectionPath, { recursive: true });
|
||||
|
||||
// Generate output file path
|
||||
const outputFilePath = path.join(collectionPath, `${title}.wav`);
|
||||
|
||||
// Read the original WAV file
|
||||
const originalWaveFile = new wavefile.WaveFile(
|
||||
await fs.readFile(originalFilePath),
|
||||
);
|
||||
|
||||
// Calculate trim points in samples
|
||||
const sampleRate = originalWaveFile.fmt.sampleRate;
|
||||
const startSample = Math.floor(trimStart * sampleRate);
|
||||
const endSample = Math.floor(trimEnd * sampleRate);
|
||||
|
||||
// Extract trimmed audio samples
|
||||
const originalSamples = originalWaveFile.getSamples(false);
|
||||
const trimmedSamples = [
|
||||
originalSamples[0].slice(startSample, endSample),
|
||||
originalSamples[1].slice(startSample, endSample),
|
||||
];
|
||||
|
||||
// Normalize samples if they are Int16 or Int32
|
||||
let normalizedSamples;
|
||||
const bitDepth = originalWaveFile.fmt.bitsPerSample;
|
||||
|
||||
// if (bitDepth === 16) {
|
||||
// // For 16-bit audio, convert to Float32
|
||||
// normalizedSamples = [new Float32Array(trimmedSamples[0].length),new Float32Array(trimmedSamples[0].length)];
|
||||
// for (let i = 0; i < trimmedSamples[0].length; i++) {
|
||||
// normalizedSamples[0][i] = trimmedSamples[0][i] / 32768.0;
|
||||
// normalizedSamples[1][i] = trimmedSamples[1][i] / 32768.0;
|
||||
// }
|
||||
// } else if (bitDepth === 32) {
|
||||
// // For 32-bit float audio, just convert to Float32
|
||||
// normalizedSamples = [new Float32Array(trimmedSamples[0]),new Float32Array(trimmedSamples[1])];
|
||||
// } else {
|
||||
// throw new Error(`Unsupported bit depth: ${bitDepth}`);
|
||||
// }
|
||||
|
||||
// Create a new WaveFile with normalized samples
|
||||
const trimmedWaveFile = new wavefile.WaveFile();
|
||||
trimmedWaveFile.fromScratch(
|
||||
originalWaveFile.fmt.numChannels,
|
||||
sampleRate,
|
||||
bitDepth, // Always use 32-bit float
|
||||
trimmedSamples,
|
||||
);
|
||||
|
||||
// Write the trimmed WAV file
|
||||
await fs.writeFile(outputFilePath, trimmedWaveFile.toBuffer());
|
||||
|
||||
return {
|
||||
success: true,
|
||||
filePath: outputFilePath,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error saving trimmed audio:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
};
|
||||
}
|
||||
},
|
||||
);
|
||||
ipcMain.handle('delete-file', async (event, filePath) => {
|
||||
try {
|
||||
const settings = await loadSettings();
|
||||
return metadataManager.deletefile(filePath, settings.outputFolder);
|
||||
} catch (error) {
|
||||
console.error('Error Deleting file:', error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('add-new-collection', (event, collectionName) => {
|
||||
try {
|
||||
return metadataManager.addNewCollection(collectionName);
|
||||
} catch (error) {
|
||||
console.error('Error adding collection:', error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
ipcMain.handle('get-trim-info', (event, collectionName, filePath) => {
|
||||
return metadataManager.getTrimInfo(collectionName, filePath);
|
||||
});
|
||||
ipcMain.handle(
|
||||
'set-trim-info',
|
||||
(event, collectionName, filePath, trim_info) => {
|
||||
return metadataManager.setTrimInfo(collectionName, filePath, trim_info);
|
||||
},
|
||||
);
|
||||
|
||||
// Add these IPC handlers
|
||||
ipcMain.handle('select-output-folder', async (event) => {
|
||||
const result = await dialog.showOpenDialog({
|
||||
properties: ['openDirectory'],
|
||||
});
|
||||
return result.filePaths[0] || '';
|
||||
});
|
||||
|
||||
ipcMain.handle('get-default-settings', () => {
|
||||
return {
|
||||
recordingLength: 30,
|
||||
outputFolder: path.join(os.homedir(), 'AudioTrimmer'),
|
||||
inputDevice: null,
|
||||
};
|
||||
});
|
||||
|
||||
ipcMain.handle('save-settings', async (event, settings) => {
|
||||
try {
|
||||
// Ensure output folder exists
|
||||
await fs.mkdir(settings.outputFolder, { recursive: true });
|
||||
|
||||
// Save settings to a file
|
||||
const settingsPath = path.join(app.getPath('userData'), 'settings.json');
|
||||
await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2));
|
||||
restartService();
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Error saving settings:', error);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('load-settings', async () => {
|
||||
return loadSettings();
|
||||
});
|
||||
|
||||
ipcMain.handle('get-input-devices', async () => {
|
||||
return await listAudioDevices();
|
||||
});
|
||||
|
||||
// Minimize to tray instead of closing
|
||||
mainWindow.on('close', (event) => {
|
||||
event.preventDefault();
|
||||
mainWindow.hide();
|
||||
});
|
||||
|
||||
// Create system tray
|
||||
createTray();
|
||||
|
||||
// Launch Python audio service
|
||||
createPythonService();
|
||||
}
|
||||
app.disableHardwareAcceleration();
|
||||
app.whenReady().then(createWindow);
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
// Do nothing - we handle closing via tray
|
||||
});
|
||||
|
||||
// Ensure Python service is killed when app quits
|
||||
app.on('before-quit', () => {
|
||||
if (audioServiceProcess) {
|
||||
try {
|
||||
if (process.platform === 'win32') {
|
||||
spawn('taskkill', ['/pid', audioServiceProcess.pid, '/f', '/t']);
|
||||
} else {
|
||||
audioServiceProcess.kill('SIGTERM');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error killing audio service:', error);
|
||||
}
|
||||
}
|
||||
});
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow();
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user