loukai-app 0.3.22 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +5 -5
  2. package/package.json +2 -2
  3. package/src/main/audioEngine.js +11 -10
  4. package/src/main/creator/conversionService.js +9 -8
  5. package/src/main/creator/downloadManager.js +70 -67
  6. package/src/main/creator/ffmpegService.js +3 -2
  7. package/src/main/creator/installLogger.js +5 -7
  8. package/src/main/creator/llmService.js +13 -7
  9. package/src/main/creator/lrclibService.js +5 -6
  10. package/src/main/creator/pythonRunner.js +3 -2
  11. package/src/main/creator/stemBuilder.js +29 -28
  12. package/src/main/handlers/audioHandlers.js +2 -1
  13. package/src/main/handlers/canvasHandlers.js +4 -3
  14. package/src/main/handlers/creatorHandlers.js +2 -1
  15. package/src/main/handlers/editorHandlers.js +8 -7
  16. package/src/main/handlers/fileHandlers.js +1 -1
  17. package/src/main/handlers/index.js +21 -19
  18. package/src/main/handlers/libraryHandlers.js +3 -0
  19. package/src/main/handlers/queueHandlers.js +3 -2
  20. package/src/main/handlers/settingsHandlers.js +2 -1
  21. package/src/main/handlers/webServerHandlers.js +3 -2
  22. package/src/main/logger.js +12 -0
  23. package/src/main/main.js +71 -66
  24. package/src/main/statePersistence.js +13 -12
  25. package/src/main/utils/pathValidator.js +21 -17
  26. package/src/main/webServer.js +102 -95
  27. package/src/renderer/components/creator/CreateTab.jsx +6 -6
  28. package/src/renderer/dist/assets/{kaiPlayer-CoMx__a_.js → kaiPlayer-DSaY7TxC.js} +2 -2
  29. package/src/renderer/dist/assets/kaiPlayer-DSaY7TxC.js.map +1 -0
  30. package/src/renderer/dist/assets/songLoaders-CcYVonLu.js +2 -0
  31. package/src/renderer/dist/assets/{songLoaders-BaTgGib4.js.map → songLoaders-CcYVonLu.js.map} +1 -1
  32. package/src/renderer/dist/renderer.css +1 -1
  33. package/src/renderer/dist/renderer.js +11 -51
  34. package/src/renderer/dist/renderer.js.map +1 -1
  35. package/src/renderer/js/kaiPlayer.js +9 -7
  36. package/src/renderer/lib/cdgraphics.js +0 -1
  37. package/src/shared/services/creatorService.js +2 -2
  38. package/src/shared/services/libraryService.js +4 -1
  39. package/src/shared/services/serverSettingsService.js +0 -1
  40. package/src/shared/services/settingsService.js +0 -2
  41. package/src/utils/m4aLoader.js +1 -1
  42. package/src/web/dist/assets/index-CGbmW1VG.js +11 -0
  43. package/src/web/dist/assets/{index-0H-RnRrV.js.map → index-CGbmW1VG.js.map} +1 -1
  44. package/src/web/dist/assets/index-GLKJK41r.css +1 -0
  45. package/src/web/dist/index.html +2 -2
  46. package/src/renderer/dist/assets/kaiPlayer-CoMx__a_.js.map +0 -1
  47. package/src/renderer/dist/assets/songLoaders-BaTgGib4.js +0 -2
  48. package/src/web/dist/assets/index-0H-RnRrV.js +0 -51
  49. package/src/web/dist/assets/index-DYW2zB0u.css +0 -1
@@ -1,3 +1,4 @@
1
+ import { log } from '../logger.js';
1
2
  /**
2
3
  * LLM Service for lyrics correction
3
4
  * Supports OpenAI, Anthropic Claude, Google Gemini, and local LM Studio
@@ -49,7 +50,7 @@ function getLLMProvider(settings) {
49
50
  */
50
51
  export async function correctLyrics(whisperOutput, referenceLyrics, settings) {
51
52
  if (!settings.enabled) {
52
- console.log('🤖 LLM correction disabled, using Whisper output as-is');
53
+ log('🤖 LLM correction disabled, using Whisper output as-is');
53
54
  return {
54
55
  output: whisperOutput,
55
56
  stats: null,
@@ -57,14 +58,14 @@ export async function correctLyrics(whisperOutput, referenceLyrics, settings) {
57
58
  }
58
59
 
59
60
  if (!referenceLyrics || !referenceLyrics.trim()) {
60
- console.log('🤖 No reference lyrics provided, skipping LLM correction');
61
+ log('🤖 No reference lyrics provided, skipping LLM correction');
61
62
  return {
62
63
  output: whisperOutput,
63
64
  stats: null,
64
65
  };
65
66
  }
66
67
 
67
- console.log(`🤖 Starting LLM lyrics correction with ${settings.provider}...`);
68
+ log(`🤖 Starting LLM lyrics correction with ${settings.provider}...`);
68
69
 
69
70
  try {
70
71
  const provider = getLLMProvider(settings);
@@ -77,9 +78,9 @@ export async function correctLyrics(whisperOutput, referenceLyrics, settings) {
77
78
  missingLines,
78
79
  } = parseCorrection(llmResponse, whisperOutput);
79
80
 
80
- console.log(`✅ LLM correction complete (${corrections.length} lines changed)`);
81
+ log(`✅ LLM correction complete (${corrections.length} lines changed)`);
81
82
  if (missingLines.length > 0) {
82
- console.log(`📝 LLM suggested ${missingLines.length} missing lines`);
83
+ log(`📝 LLM suggested ${missingLines.length} missing lines`);
83
84
  }
84
85
 
85
86
  return {
@@ -101,7 +102,7 @@ export async function correctLyrics(whisperOutput, referenceLyrics, settings) {
101
102
  };
102
103
  } catch (error) {
103
104
  console.error('❌ LLM correction failed:', error.message);
104
- console.log('⚠️ Falling back to original Whisper output');
105
+ log('⚠️ Falling back to original Whisper output');
105
106
  return {
106
107
  output: whisperOutput,
107
108
  stats: {
@@ -304,7 +305,12 @@ export function getLLMSettings(settingsManager) {
304
305
  const apiKey = llmConfig.apiKey || LLM_DEFAULTS.apiKey;
305
306
 
306
307
  // SECURITY FIX (#25): Mask API key - only show last 4 chars to renderer
307
- const maskedApiKey = apiKey && apiKey.length > 8 ? `${'•'.repeat(apiKey.length - 4)}${apiKey.slice(-4)}` : apiKey ? '••••••••' : '';
308
+ const maskedApiKey =
309
+ apiKey && apiKey.length > 8
310
+ ? `${'•'.repeat(apiKey.length - 4)}${apiKey.slice(-4)}`
311
+ : apiKey
312
+ ? '••••••••'
313
+ : '';
308
314
 
309
315
  return {
310
316
  enabled: llmConfig.enabled ?? LLM_DEFAULTS.enabled,
@@ -1,3 +1,4 @@
1
+ import { log } from '../logger.js';
1
2
  /**
2
3
  * LRCLIB Service - Lyrics lookup from lrclib.net
3
4
  *
@@ -73,7 +74,7 @@ export async function searchLyrics(title, artist) {
73
74
  }
74
75
 
75
76
  const url = `${LRCLIB_API_BASE}/search?${params}`;
76
- console.log(`Searching LRCLIB for: ${title} by ${artist || 'unknown'}`);
77
+ log(`Searching LRCLIB for: ${title} by ${artist || 'unknown'}`);
77
78
 
78
79
  const response = await fetch(url, {
79
80
  headers: {
@@ -97,9 +98,7 @@ export async function searchLyrics(title, artist) {
97
98
  // Find first non-instrumental result with plain lyrics
98
99
  for (const result of results) {
99
100
  if (!result.instrumental && result.plainLyrics) {
100
- console.log(
101
- `Found lyrics: ${result.name || 'Unknown'} from ${result.albumName || 'Unknown'}`
102
- );
101
+ log(`Found lyrics: ${result.name || 'Unknown'} from ${result.albumName || 'Unknown'}`);
103
102
  return {
104
103
  id: result.id,
105
104
  name: result.name,
@@ -128,7 +127,7 @@ export async function searchLyrics(title, artist) {
128
127
  export async function getLyricsById(id) {
129
128
  try {
130
129
  const url = `${LRCLIB_API_BASE}/get/${id}`;
131
- console.log(`Fetching LRCLIB track: ${id}`);
130
+ log(`Fetching LRCLIB track: ${id}`);
132
131
 
133
132
  const response = await fetch(url, {
134
133
  headers: {
@@ -278,7 +277,7 @@ export async function prepareWhisperContext(title, artist, existingLyrics = null
278
277
 
279
278
  if (vocabularyHints) {
280
279
  initialPrompt = `${title}. ${vocabularyHints}`;
281
- console.log(`Whisper initial prompt: ${initialPrompt.substring(0, 100)}...`);
280
+ log(`Whisper initial prompt: ${initialPrompt.substring(0, 100)}...`);
282
281
  } else {
283
282
  initialPrompt = title;
284
283
  }
@@ -76,9 +76,10 @@ export function runPythonScript(
76
76
  const lines = text.split('\n');
77
77
 
78
78
  for (const line of lines) {
79
- // Emit raw console output
79
+ // Emit raw console output (round long floats so tqdm progress doesn't jitter)
80
80
  if (onConsoleOutput && line.trim()) {
81
- onConsoleOutput(line);
81
+ const cleaned = line.replace(/\d+\.\d{3,}/g, (m) => parseFloat(m).toFixed(2));
82
+ onConsoleOutput(cleaned);
82
83
  }
83
84
 
84
85
  // Parse our PROGRESS: updates
@@ -1,7 +1,8 @@
1
+ import { log } from '../logger.js';
1
2
  /**
2
- * Stem Builder - Creates .stem.m4a files with embedded stem data
3
+ * Stem Builder - Creates .stem.mp4 files with embedded stem data
3
4
  *
4
- * The .stem.m4a format embeds multiple audio stems in a single M4A container
5
+ * The .stem.mp4 format embeds multiple audio stems in a single M4A container
5
6
  * using custom atoms/boxes. This is compatible with Native Instruments Stems.
6
7
  *
7
8
  * Structure:
@@ -20,10 +21,10 @@ import { getFFmpegPath } from './systemChecker.js';
20
21
  import { Atoms as M4AAtoms } from 'm4a-stems';
21
22
 
22
23
  /**
23
- * Build a .stem.m4a file from individual stem files
24
+ * Build a .stem.mp4 file from individual stem files
24
25
  *
25
26
  * @param {Object} options - Build options
26
- * @param {string} options.outputPath - Output .stem.m4a path
27
+ * @param {string} options.outputPath - Output .stem.mp4 path
27
28
  * @param {Object} options.stems - Map of stem name to path
28
29
  * @param {Object} options.metadata - Song metadata (title, artist, duration)
29
30
  * @param {Object} options.lyrics - Whisper transcription result with word timestamps
@@ -107,7 +108,7 @@ export async function buildStemM4a(options) {
107
108
 
108
109
  // Log key if detected
109
110
  if (pitch?.detected_key?.key) {
110
- console.log(`🎵 Writing key to metadata: ${pitch.detected_key.key}`);
111
+ log(`🎵 Writing key to metadata: ${pitch.detected_key.key}`);
111
112
  }
112
113
 
113
114
  // Copy codecs (stems are already AAC)
@@ -160,7 +161,7 @@ export async function buildStemM4a(options) {
160
161
  // Per NI Stems spec, stems array should have exactly 4 entries (NOT including master)
161
162
  // Track order: drums, bass, other, vocals (corresponding to tracks 2-5)
162
163
  const stemPartsOnly = stemNames.filter((name) => name !== 'master');
163
- console.log(
164
+ log(
164
165
  `🎛️ Writing NI Stems metadata for ${stemPartsOnly.length} stem parts: ${stemPartsOnly.join(', ')}`
165
166
  );
166
167
  await M4AAtoms.addNiStemsMetadata(outputPath, stemPartsOnly);
@@ -168,7 +169,7 @@ export async function buildStemM4a(options) {
168
169
  // Verify stem atom was written (debug)
169
170
  const { stat } = await import('fs/promises');
170
171
  const afterStemSize = (await stat(outputPath)).size;
171
- console.log(`📊 File size after stem atom: ${afterStemSize} bytes`);
172
+ log(`📊 File size after stem atom: ${afterStemSize} bytes`);
172
173
 
173
174
  // Now inject kara atom for karaoke data using m4a-stems library
174
175
  await injectKaraokeAtoms(outputPath, {
@@ -270,26 +271,26 @@ async function injectKaraokeAtoms(filePath, data) {
270
271
  }
271
272
 
272
273
  // Write kara atom using m4a-stems library
273
- console.log(`💾 Writing kara atom: ${lines.length} lines`);
274
+ log(`💾 Writing kara atom: ${lines.length} lines`);
274
275
  await M4AAtoms.writeKaraAtom(filePath, karaData);
275
276
 
276
277
  // Verify final file size (debug)
277
278
  const { stat } = await import('fs/promises');
278
279
  const finalSize = (await stat(filePath)).size;
279
- console.log(`📊 Final file size after kara atom: ${finalSize} bytes`);
280
+ log(`📊 Final file size after kara atom: ${finalSize} bytes`);
280
281
 
281
282
  // Note: Vocal pitch tracking is done at runtime, not stored in file.
282
283
  // CREPE output is used only for key detection (stored in standard metadata).
283
284
 
284
- console.log('✅ Karaoke atoms written successfully');
285
+ log('✅ Karaoke atoms written successfully');
285
286
  }
286
287
 
287
288
  /**
288
- * Inject lyrics into an existing .stem.m4a file
289
+ * Inject lyrics into an existing .stem.mp4 file
289
290
  * Used for "lyrics only" mode when stems already exist
290
291
  *
291
292
  * @param {Object} options - Injection options
292
- * @param {string} options.filePath - Path to existing .stem.m4a file
293
+ * @param {string} options.filePath - Path to existing .stem.mp4 file
293
294
  * @param {Object} options.lyrics - Whisper transcription result with word timestamps
294
295
  * @param {Object} options.llmCorrections - LLM correction stats
295
296
  * @param {string[]} options.tags - Tags array for filtering
@@ -298,7 +299,7 @@ async function injectKaraokeAtoms(filePath, data) {
298
299
  export async function injectLyricsIntoStemFile(options) {
299
300
  const { filePath, lyrics, llmCorrections, tags } = options;
300
301
 
301
- console.log(`🎤 Injecting lyrics into existing stem file: ${filePath}`);
302
+ log(`🎤 Injecting lyrics into existing stem file: ${filePath}`);
302
303
 
303
304
  // Read existing kara atom to preserve timing/tags
304
305
  let existingKara = null;
@@ -376,23 +377,23 @@ export async function injectLyricsIntoStemFile(options) {
376
377
  }
377
378
 
378
379
  // Write kara atom
379
- console.log(`💾 Writing kara atom: ${lines.length} lines`);
380
+ log(`💾 Writing kara atom: ${lines.length} lines`);
380
381
  await M4AAtoms.writeKaraAtom(filePath, karaData);
381
382
 
382
- console.log('✅ Lyrics injected successfully');
383
+ log('✅ Lyrics injected successfully');
383
384
  }
384
385
 
385
386
  /**
386
- * Repair an existing .stem.m4a file to fix NI Stems metadata
387
+ * Repair an existing .stem.mp4 file to fix NI Stems metadata
387
388
  * This fixes files created before the spec-compliant stem atom was implemented
388
389
  *
389
- * @param {string} filePath - Path to existing .stem.m4a file
390
+ * @param {string} filePath - Path to existing .stem.mp4 file
390
391
  * @param {Object} options - Repair options
391
392
  * @param {boolean} options.force - Force rewrite even if metadata exists
392
393
  * @returns {Promise<Object>} Repair result
393
394
  */
394
395
  export async function repairStemFile(filePath, options = {}) {
395
- console.log(`🔧 Checking stem file: ${filePath}`);
396
+ log(`🔧 Checking stem file: ${filePath}`);
396
397
 
397
398
  // Default NI Stems order (excluding master, which is track 0)
398
399
  const stemPartsOnly = ['drums', 'bass', 'other', 'vocals'];
@@ -408,8 +409,8 @@ export async function repairStemFile(filePath, options = {}) {
408
409
 
409
410
  if (existingMetadata && existingMetadata.stems && !options.force) {
410
411
  const existingStems = existingMetadata.stems.map((s) => s.name).join(', ');
411
- console.log(`✅ File already has valid NI Stems metadata: ${existingStems}`);
412
- console.log(' Use --force to rewrite anyway.');
412
+ log(`✅ File already has valid NI Stems metadata: ${existingStems}`);
413
+ log(' Use --force to rewrite anyway.');
413
414
  return {
414
415
  success: true,
415
416
  filePath,
@@ -420,15 +421,15 @@ export async function repairStemFile(filePath, options = {}) {
420
421
 
421
422
  // Write the stem atom with correct 4-stem metadata
422
423
  if (existingMetadata) {
423
- console.log(`🔄 Force rewriting NI Stems metadata for ${stemPartsOnly.length} stem parts`);
424
+ log(`🔄 Force rewriting NI Stems metadata for ${stemPartsOnly.length} stem parts`);
424
425
  } else {
425
- console.log(`🎛️ Adding NI Stems metadata for ${stemPartsOnly.length} stem parts`);
426
+ log(`🎛️ Adding NI Stems metadata for ${stemPartsOnly.length} stem parts`);
426
427
  }
427
428
  await M4AAtoms.addNiStemsMetadata(filePath, stemPartsOnly);
428
429
 
429
- console.log('✅ Stem file repaired successfully');
430
- console.log('⚠️ Note: Track disposition flags cannot be fixed without re-encoding.');
431
- console.log(' File should work in Mixxx/Traktor but may play wrong track in some players.');
430
+ log('✅ Stem file repaired successfully');
431
+ log('⚠️ Note: Track disposition flags cannot be fixed without re-encoding.');
432
+ log(' File should work in Mixxx/Traktor but may play wrong track in some players.');
432
433
 
433
434
  return {
434
435
  success: true,
@@ -446,12 +447,12 @@ export async function repairStemFile(filePath, options = {}) {
446
447
 
447
448
  /**
448
449
  * Batch repair multiple stem files
449
- * @param {string[]} filePaths - Array of paths to .stem.m4a files
450
+ * @param {string[]} filePaths - Array of paths to .stem.mp4 files
450
451
  * @param {Object} options - Repair options (passed to each repairStemFile call)
451
452
  * @returns {Promise<Object>} Batch repair results
452
453
  */
453
454
  export async function repairStemFiles(filePaths, options = {}) {
454
- console.log(`🔧 Batch checking ${filePaths.length} stem files...`);
455
+ log(`🔧 Batch checking ${filePaths.length} stem files...`);
455
456
 
456
457
  const results = {
457
458
  total: filePaths.length,
@@ -477,7 +478,7 @@ export async function repairStemFiles(filePaths, options = {}) {
477
478
  }
478
479
  }
479
480
 
480
- console.log(
481
+ log(
481
482
  `\n📊 Complete: ${results.alreadyValid} already valid, ${results.repaired} repaired, ${results.failed} failed`
482
483
  );
483
484
  return results;
@@ -1,3 +1,4 @@
1
+ import { log } from '../logger.js';
1
2
  /**
2
3
  * Audio IPC Handlers
3
4
  * Handles all audio device enumeration and routing
@@ -24,7 +25,7 @@ export function registerAudioHandlers(mainApp) {
24
25
 
25
26
  // Set audio device
26
27
  ipcMain.handle(AUDIO_CHANNELS.SET_DEVICE, (event, deviceType, deviceId) => {
27
- console.log(`🎧 IPC: Setting ${deviceType} device to ${deviceId}`);
28
+ log(`🎧 IPC: Setting ${deviceType} device to ${deviceId}`);
28
29
  if (mainApp.audioEngine) {
29
30
  return mainApp.audioEngine.setDevice(deviceType, deviceId);
30
31
  }
@@ -1,3 +1,4 @@
1
+ import { log } from '../logger.js';
1
2
  /**
2
3
  * Canvas Window IPC Handlers
3
4
  * Handles canvas window management and WebRTC streaming operations
@@ -42,7 +43,7 @@ export function registerCanvasHandlers(mainApp) {
42
43
 
43
44
  // Canvas window ready signal
44
45
  ipcMain.on('canvas:childReady', () => {
45
- console.log('Child window ready, starting canvas streaming');
46
+ log('Child window ready, starting canvas streaming');
46
47
  // Small delay to ensure everything is fully initialized
47
48
  setTimeout(() => {
48
49
  mainApp.startCanvasStreaming();
@@ -51,7 +52,7 @@ export function registerCanvasHandlers(mainApp) {
51
52
 
52
53
  // Relay ICE candidates between sender and receiver
53
54
  ipcMain.handle('canvas:sendICECandidate', (event, source, candidate) => {
54
- console.log('🧊 Relaying ICE candidate from', source);
55
+ log('🧊 Relaying ICE candidate from', source);
55
56
  if (source === 'sender') {
56
57
  // Send to receiver via IPC
57
58
  if (mainApp.canvasWindow && !mainApp.canvasWindow.isDestroyed()) {
@@ -68,7 +69,7 @@ export function registerCanvasHandlers(mainApp) {
68
69
  // Toggle canvas window fullscreen
69
70
  ipcMain.handle('canvas:toggleFullscreen', (event, shouldBeFullscreen) => {
70
71
  if (mainApp.canvasWindow && !mainApp.canvasWindow.isDestroyed()) {
71
- console.log('🖥️ Toggling canvas window fullscreen:', shouldBeFullscreen);
72
+ log('🖥️ Toggling canvas window fullscreen:', shouldBeFullscreen);
72
73
  mainApp.canvasWindow.setFullScreen(shouldBeFullscreen);
73
74
  return { success: true, fullscreen: shouldBeFullscreen };
74
75
  }
@@ -1,3 +1,4 @@
1
+ import { log } from '../logger.js';
1
2
  /**
2
3
  * Creator IPC Handlers
3
4
  * Handles AI tool installation and karaoke file creation
@@ -155,5 +156,5 @@ export function registerCreatorHandlers(mainApp) {
155
156
  return llmService.testLLMConnection(settings);
156
157
  });
157
158
 
158
- console.log('✅ Creator handlers registered');
159
+ log('✅ Creator handlers registered');
159
160
  }
@@ -1,3 +1,4 @@
1
+ import { log } from '../logger.js';
1
2
  /**
2
3
  * Editor IPC Handlers
3
4
  * Handles song editing operations (KAI and M4A formats)
@@ -13,12 +14,12 @@ export function registerEditorHandlers(mainApp) {
13
14
  // Load song file for editing (KAI or M4A)
14
15
  ipcMain.handle('editor:loadKai', async (event, filePath) => {
15
16
  try {
16
- console.log('Load song file for editing:', filePath);
17
+ log('Load song file for editing:', filePath);
17
18
 
18
19
  const editorService = await import('../../shared/services/editorService.js');
19
20
  const result = await editorService.loadSong(filePath);
20
21
 
21
- console.log(
22
+ log(
22
23
  `${result.format.toUpperCase()} file loaded for editing, has lyrics:`,
23
24
  result.kaiData.lyrics?.length || 0
24
25
  );
@@ -37,8 +38,8 @@ export function registerEditorHandlers(mainApp) {
37
38
  // Save song file (KAI or M4A)
38
39
  ipcMain.handle('editor:saveKai', async (event, kaiData, originalPath) => {
39
40
  try {
40
- console.log('Save song file request:', originalPath);
41
- console.log('Updated lyrics:', kaiData.lyrics?.length || 0, 'lines');
41
+ log('Save song file request:', originalPath);
42
+ log('Updated lyrics:', kaiData.lyrics?.length || 0, 'lines');
42
43
 
43
44
  // Determine format from file extension
44
45
  const lowerPath = originalPath.toLowerCase();
@@ -58,7 +59,7 @@ export function registerEditorHandlers(mainApp) {
58
59
  lyrics: kaiData.lyrics,
59
60
  });
60
61
 
61
- console.log(`${format.toUpperCase()} file saved successfully`);
62
+ log(`${format.toUpperCase()} file saved successfully`);
62
63
  return { success: true };
63
64
  } catch (error) {
64
65
  console.error('Failed to save song file:', error);
@@ -69,7 +70,7 @@ export function registerEditorHandlers(mainApp) {
69
70
  // Reload song file in player (KAI or M4A)
70
71
  ipcMain.handle('editor:reloadKai', async (event, filePath) => {
71
72
  try {
72
- console.log('Reload song file request:', filePath);
73
+ log('Reload song file request:', filePath);
73
74
 
74
75
  // Determine format and call appropriate loader
75
76
  const lowerPath = filePath.toLowerCase();
@@ -84,7 +85,7 @@ export function registerEditorHandlers(mainApp) {
84
85
  }
85
86
 
86
87
  if (result && result.success) {
87
- console.log('Song file reloaded successfully');
88
+ log('Song file reloaded successfully');
88
89
  return { success: true };
89
90
  } else {
90
91
  console.error('Failed to reload song file');
@@ -32,7 +32,7 @@ export function registerFileHandlers(mainApp) {
32
32
  ipcMain.handle('file:loadKaiFromPath', async (event, filePath) => {
33
33
  // Get the songs folder from settings
34
34
  const songsFolder = mainApp.settings?.getSongsFolder?.();
35
-
35
+
36
36
  // Validate the path is within the songs directory
37
37
  const validation = validateSongPath(filePath, songsFolder);
38
38
  if (!validation.valid) {
@@ -3,47 +3,49 @@
3
3
  * Central registration point for all IPC handlers
4
4
  */
5
5
 
6
- console.log('📦 Loading handler modules...');
6
+ import { log } from '../logger.js';
7
+
8
+ log('📦 Loading handler modules...');
7
9
 
8
10
  import { registerAudioHandlers } from './audioHandlers.js';
9
- console.log('✓ audioHandlers');
11
+ log('✓ audioHandlers');
10
12
  import { registerMixerHandlers } from './mixerHandlers.js';
11
- console.log('✓ mixerHandlers');
13
+ log('✓ mixerHandlers');
12
14
  import { registerPlayerHandlers } from './playerHandlers.js';
13
- console.log('✓ playerHandlers');
15
+ log('✓ playerHandlers');
14
16
  import { registerLibraryHandlers } from './libraryHandlers.js';
15
- console.log('✓ libraryHandlers');
17
+ log('✓ libraryHandlers');
16
18
  import { registerSettingsHandlers } from './settingsHandlers.js';
17
- console.log('✓ settingsHandlers');
19
+ log('✓ settingsHandlers');
18
20
  import { registerQueueHandlers } from './queueHandlers.js';
19
- console.log('✓ queueHandlers');
21
+ log('✓ queueHandlers');
20
22
  import { registerWebServerHandlers } from './webServerHandlers.js';
21
- console.log('✓ webServerHandlers');
23
+ log('✓ webServerHandlers');
22
24
  import { registerCanvasHandlers } from './canvasHandlers.js';
23
- console.log('✓ canvasHandlers');
25
+ log('✓ canvasHandlers');
24
26
  import { registerEffectsHandlers } from './effectsHandlers.js';
25
- console.log('✓ effectsHandlers');
27
+ log('✓ effectsHandlers');
26
28
  import { registerEditorHandlers } from './editorHandlers.js';
27
- console.log('✓ editorHandlers');
29
+ log('✓ editorHandlers');
28
30
  import { registerPreferencesHandlers } from './preferencesHandlers.js';
29
- console.log('✓ preferencesHandlers');
31
+ log('✓ preferencesHandlers');
30
32
  import { registerFileHandlers } from './fileHandlers.js';
31
- console.log('✓ fileHandlers');
33
+ log('✓ fileHandlers');
32
34
  import { registerRendererHandlers } from './rendererHandlers.js';
33
- console.log('✓ rendererHandlers');
35
+ log('✓ rendererHandlers');
34
36
  import { registerAppHandlers } from './appHandlers.js';
35
- console.log('✓ appHandlers');
37
+ log('✓ appHandlers');
36
38
  import { registerAutotuneHandlers } from './autotuneHandlers.js';
37
- console.log('✓ autotuneHandlers');
39
+ log('✓ autotuneHandlers');
38
40
  import { registerCreatorHandlers } from './creatorHandlers.js';
39
- console.log('✓ creatorHandlers');
41
+ log('✓ creatorHandlers');
40
42
 
41
43
  /**
42
44
  * Register all IPC handlers
43
45
  * @param {Object} mainApp - Main application instance
44
46
  */
45
47
  export function registerAllHandlers(mainApp) {
46
- console.log('📡 Registering IPC handlers...');
48
+ log('📡 Registering IPC handlers...');
47
49
 
48
50
  try {
49
51
  // Core handlers
@@ -70,7 +72,7 @@ export function registerAllHandlers(mainApp) {
70
72
  // Creator handlers
71
73
  registerCreatorHandlers(mainApp);
72
74
 
73
- console.log('✅ All IPC handlers registered');
75
+ log('✅ All IPC handlers registered');
74
76
  } catch (error) {
75
77
  console.error('❌ Failed to register IPC handlers:', error);
76
78
  throw error;
@@ -58,6 +58,9 @@ export function registerLibraryHandlers(mainApp) {
58
58
  // Update all caches (mainApp, webServer, disk)
59
59
  await libraryService.updateLibraryCache(mainApp, result.files);
60
60
 
61
+ // Notify renderer so LibraryPanel reloads (matches scanLibrary behavior)
62
+ mainApp.sendToRenderer('library:scanComplete', { count: result.files.length });
63
+
61
64
  // Return with 'songs' key for renderer compatibility
62
65
  return {
63
66
  ...result,
@@ -1,3 +1,4 @@
1
+ import { log } from '../logger.js';
1
2
  /**
2
3
  * Queue IPC Handlers
3
4
  * Handles song queue management operations
@@ -21,11 +22,11 @@ export function registerQueueHandlers(mainApp) {
21
22
 
22
23
  // If queue was empty, automatically load and start playing the first song
23
24
  if (result.success && result.wasEmpty) {
24
- console.log(`🎵 Queue was empty, auto-loading "${result.queueItem.title}"`);
25
+ log(`🎵 Queue was empty, auto-loading "${result.queueItem.title}"`);
25
26
  try {
26
27
  // Use the returned queueItem which has the generated ID
27
28
  await mainApp.loadKaiFile(result.queueItem.path, result.queueItem.id);
28
- console.log('✅ Successfully auto-loaded song from queue');
29
+ log('✅ Successfully auto-loaded song from queue');
29
30
  } catch (error) {
30
31
  console.error('❌ Failed to auto-load song from queue:', error);
31
32
  }
@@ -1,3 +1,4 @@
1
+ import { log } from '../logger.js';
1
2
  /**
2
3
  * Settings IPC Handlers
3
4
  * Handles application settings persistence
@@ -18,7 +19,7 @@ import {
18
19
  * @param {Object} _mainApp - Main application instance (unused, kept for signature consistency)
19
20
  */
20
21
  export function registerSettingsHandlers(_mainApp) {
21
- console.log('📡 Registering settings handlers...');
22
+ log('📡 Registering settings handlers...');
22
23
 
23
24
  // Get setting - uses settingsService which applies defaults
24
25
  ipcMain.handle(SETTINGS_CHANNELS.GET, (event, key, defaultValue) => {
@@ -1,3 +1,4 @@
1
+ import { log } from '../logger.js';
1
2
  /**
2
3
  * Web Server IPC Handlers
3
4
  * Handles all web server management operations
@@ -13,7 +14,7 @@ import * as requestsService from '../../shared/services/requestsService.js';
13
14
  * @param {Object} mainApp - Main application instance
14
15
  */
15
16
  export function registerWebServerHandlers(mainApp) {
16
- console.log('📡 Registering web server handlers...');
17
+ log('📡 Registering web server handlers...');
17
18
 
18
19
  // Get web server port
19
20
  ipcMain.handle('webServer:getPort', () => {
@@ -22,7 +23,7 @@ export function registerWebServerHandlers(mainApp) {
22
23
 
23
24
  // Get web server URL
24
25
  ipcMain.handle('webServer:getUrl', () => {
25
- // console.log('🔍 webServer:getUrl called', { hasWebServer: Boolean(mainApp.webServer) });
26
+ // log('🔍 webServer:getUrl called', { hasWebServer: Boolean(mainApp.webServer) });
26
27
  return mainApp.webServer?.getServerUrl() || null;
27
28
  });
28
29
 
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Simple logger that only outputs in development mode
3
+ */
4
+
5
+ const isDev = process.argv.includes('--dev');
6
+
7
+ export const log = isDev ? console.log.bind(console) : () => {};
8
+ export const info = isDev ? console.info.bind(console) : () => {};
9
+ export const warn = console.warn.bind(console); // Always show warnings
10
+ export const error = console.error.bind(console); // Always show errors
11
+
12
+ export default { log, info, warn, error };