start of react migration

This commit is contained in:
michalcourson
2026-02-04 20:42:14 -05:00
parent 51ad065047
commit 17bace5eaf
71 changed files with 26503 additions and 6037 deletions

483
electron-ui/src/old/main.js Normal file
View 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();
}
});