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.
- package/README.md +5 -5
- package/package.json +2 -2
- package/src/main/audioEngine.js +11 -10
- package/src/main/creator/conversionService.js +9 -8
- package/src/main/creator/downloadManager.js +70 -67
- package/src/main/creator/ffmpegService.js +3 -2
- package/src/main/creator/installLogger.js +5 -7
- package/src/main/creator/llmService.js +13 -7
- package/src/main/creator/lrclibService.js +5 -6
- package/src/main/creator/pythonRunner.js +3 -2
- package/src/main/creator/stemBuilder.js +29 -28
- package/src/main/handlers/audioHandlers.js +2 -1
- package/src/main/handlers/canvasHandlers.js +4 -3
- package/src/main/handlers/creatorHandlers.js +2 -1
- package/src/main/handlers/editorHandlers.js +8 -7
- package/src/main/handlers/fileHandlers.js +1 -1
- package/src/main/handlers/index.js +21 -19
- package/src/main/handlers/libraryHandlers.js +3 -0
- package/src/main/handlers/queueHandlers.js +3 -2
- package/src/main/handlers/settingsHandlers.js +2 -1
- package/src/main/handlers/webServerHandlers.js +3 -2
- package/src/main/logger.js +12 -0
- package/src/main/main.js +71 -66
- package/src/main/statePersistence.js +13 -12
- package/src/main/utils/pathValidator.js +21 -17
- package/src/main/webServer.js +102 -95
- package/src/renderer/components/creator/CreateTab.jsx +6 -6
- package/src/renderer/dist/assets/{kaiPlayer-CoMx__a_.js → kaiPlayer-DSaY7TxC.js} +2 -2
- package/src/renderer/dist/assets/kaiPlayer-DSaY7TxC.js.map +1 -0
- package/src/renderer/dist/assets/songLoaders-CcYVonLu.js +2 -0
- package/src/renderer/dist/assets/{songLoaders-BaTgGib4.js.map → songLoaders-CcYVonLu.js.map} +1 -1
- package/src/renderer/dist/renderer.css +1 -1
- package/src/renderer/dist/renderer.js +11 -51
- package/src/renderer/dist/renderer.js.map +1 -1
- package/src/renderer/js/kaiPlayer.js +9 -7
- package/src/renderer/lib/cdgraphics.js +0 -1
- package/src/shared/services/creatorService.js +2 -2
- package/src/shared/services/libraryService.js +4 -1
- package/src/shared/services/serverSettingsService.js +0 -1
- package/src/shared/services/settingsService.js +0 -2
- package/src/utils/m4aLoader.js +1 -1
- package/src/web/dist/assets/index-CGbmW1VG.js +11 -0
- package/src/web/dist/assets/{index-0H-RnRrV.js.map → index-CGbmW1VG.js.map} +1 -1
- package/src/web/dist/assets/index-GLKJK41r.css +1 -0
- package/src/web/dist/index.html +2 -2
- package/src/renderer/dist/assets/kaiPlayer-CoMx__a_.js.map +0 -1
- package/src/renderer/dist/assets/songLoaders-BaTgGib4.js +0 -2
- package/src/web/dist/assets/index-0H-RnRrV.js +0 -51
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
81
|
+
log(`✅ LLM correction complete (${corrections.length} lines changed)`);
|
|
81
82
|
if (missingLines.length > 0) {
|
|
82
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
3
|
+
* Stem Builder - Creates .stem.mp4 files with embedded stem data
|
|
3
4
|
*
|
|
4
|
-
* The .stem.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
285
|
+
log('✅ Karaoke atoms written successfully');
|
|
285
286
|
}
|
|
286
287
|
|
|
287
288
|
/**
|
|
288
|
-
* Inject lyrics into an existing .stem.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
380
|
+
log(`💾 Writing kara atom: ${lines.length} lines`);
|
|
380
381
|
await M4AAtoms.writeKaraAtom(filePath, karaData);
|
|
381
382
|
|
|
382
|
-
|
|
383
|
+
log('✅ Lyrics injected successfully');
|
|
383
384
|
}
|
|
384
385
|
|
|
385
386
|
/**
|
|
386
|
-
* Repair an existing .stem.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
412
|
-
|
|
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
|
-
|
|
424
|
+
log(`🔄 Force rewriting NI Stems metadata for ${stemPartsOnly.length} stem parts`);
|
|
424
425
|
} else {
|
|
425
|
-
|
|
426
|
+
log(`🎛️ Adding NI Stems metadata for ${stemPartsOnly.length} stem parts`);
|
|
426
427
|
}
|
|
427
428
|
await M4AAtoms.addNiStemsMetadata(filePath, stemPartsOnly);
|
|
428
429
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
41
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
6
|
+
import { log } from '../logger.js';
|
|
7
|
+
|
|
8
|
+
log('📦 Loading handler modules...');
|
|
7
9
|
|
|
8
10
|
import { registerAudioHandlers } from './audioHandlers.js';
|
|
9
|
-
|
|
11
|
+
log('✓ audioHandlers');
|
|
10
12
|
import { registerMixerHandlers } from './mixerHandlers.js';
|
|
11
|
-
|
|
13
|
+
log('✓ mixerHandlers');
|
|
12
14
|
import { registerPlayerHandlers } from './playerHandlers.js';
|
|
13
|
-
|
|
15
|
+
log('✓ playerHandlers');
|
|
14
16
|
import { registerLibraryHandlers } from './libraryHandlers.js';
|
|
15
|
-
|
|
17
|
+
log('✓ libraryHandlers');
|
|
16
18
|
import { registerSettingsHandlers } from './settingsHandlers.js';
|
|
17
|
-
|
|
19
|
+
log('✓ settingsHandlers');
|
|
18
20
|
import { registerQueueHandlers } from './queueHandlers.js';
|
|
19
|
-
|
|
21
|
+
log('✓ queueHandlers');
|
|
20
22
|
import { registerWebServerHandlers } from './webServerHandlers.js';
|
|
21
|
-
|
|
23
|
+
log('✓ webServerHandlers');
|
|
22
24
|
import { registerCanvasHandlers } from './canvasHandlers.js';
|
|
23
|
-
|
|
25
|
+
log('✓ canvasHandlers');
|
|
24
26
|
import { registerEffectsHandlers } from './effectsHandlers.js';
|
|
25
|
-
|
|
27
|
+
log('✓ effectsHandlers');
|
|
26
28
|
import { registerEditorHandlers } from './editorHandlers.js';
|
|
27
|
-
|
|
29
|
+
log('✓ editorHandlers');
|
|
28
30
|
import { registerPreferencesHandlers } from './preferencesHandlers.js';
|
|
29
|
-
|
|
31
|
+
log('✓ preferencesHandlers');
|
|
30
32
|
import { registerFileHandlers } from './fileHandlers.js';
|
|
31
|
-
|
|
33
|
+
log('✓ fileHandlers');
|
|
32
34
|
import { registerRendererHandlers } from './rendererHandlers.js';
|
|
33
|
-
|
|
35
|
+
log('✓ rendererHandlers');
|
|
34
36
|
import { registerAppHandlers } from './appHandlers.js';
|
|
35
|
-
|
|
37
|
+
log('✓ appHandlers');
|
|
36
38
|
import { registerAutotuneHandlers } from './autotuneHandlers.js';
|
|
37
|
-
|
|
39
|
+
log('✓ autotuneHandlers');
|
|
38
40
|
import { registerCreatorHandlers } from './creatorHandlers.js';
|
|
39
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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 };
|