loukai-app 0.3.0

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 (290) hide show
  1. package/README.md +558 -0
  2. package/bin/loukai.js +32 -0
  3. package/package.json +243 -0
  4. package/src/main/appState.js +250 -0
  5. package/src/main/audioEngine.js +478 -0
  6. package/src/main/creator/conversionService.js +503 -0
  7. package/src/main/creator/downloadManager.js +1128 -0
  8. package/src/main/creator/ffmpegService.js +487 -0
  9. package/src/main/creator/installLogger.js +51 -0
  10. package/src/main/creator/keyDetection.js +212 -0
  11. package/src/main/creator/llmService.js +370 -0
  12. package/src/main/creator/lrclibService.js +340 -0
  13. package/src/main/creator/python/crepe_runner.py +189 -0
  14. package/src/main/creator/python/demucs_runner.py +158 -0
  15. package/src/main/creator/python/whisper_runner.py +172 -0
  16. package/src/main/creator/pythonRunner.js +268 -0
  17. package/src/main/creator/stemBuilder.js +491 -0
  18. package/src/main/creator/systemChecker.js +474 -0
  19. package/src/main/handlers/appHandlers.js +45 -0
  20. package/src/main/handlers/audioHandlers.js +33 -0
  21. package/src/main/handlers/autotuneHandlers.js +28 -0
  22. package/src/main/handlers/canvasHandlers.js +84 -0
  23. package/src/main/handlers/creatorHandlers.js +159 -0
  24. package/src/main/handlers/editorHandlers.js +98 -0
  25. package/src/main/handlers/effectsHandlers.js +100 -0
  26. package/src/main/handlers/fileHandlers.js +45 -0
  27. package/src/main/handlers/index.js +78 -0
  28. package/src/main/handlers/libraryHandlers.js +96 -0
  29. package/src/main/handlers/mixerHandlers.js +64 -0
  30. package/src/main/handlers/playerHandlers.js +39 -0
  31. package/src/main/handlers/preferencesHandlers.js +46 -0
  32. package/src/main/handlers/queueHandlers.js +81 -0
  33. package/src/main/handlers/rendererHandlers.js +63 -0
  34. package/src/main/handlers/settingsHandlers.js +42 -0
  35. package/src/main/handlers/webServerHandlers.js +105 -0
  36. package/src/main/main.js +2351 -0
  37. package/src/main/preload.js +252 -0
  38. package/src/main/settingsManager.js +139 -0
  39. package/src/main/statePersistence.js +193 -0
  40. package/src/main/utils/pathValidator.js +112 -0
  41. package/src/main/webServer.js +2535 -0
  42. package/src/native/autotune.js +417 -0
  43. package/src/renderer/adapters/ElectronBridge.js +677 -0
  44. package/src/renderer/canvas.html +80 -0
  45. package/src/renderer/components/App.jsx +303 -0
  46. package/src/renderer/components/AppRoot.jsx +37 -0
  47. package/src/renderer/components/AudioDeviceSettings.jsx +145 -0
  48. package/src/renderer/components/EffectsPanelWrapper.jsx +267 -0
  49. package/src/renderer/components/MixerTab.jsx +233 -0
  50. package/src/renderer/components/MixerTabWrapper.jsx +31 -0
  51. package/src/renderer/components/PortalSelect.jsx +239 -0
  52. package/src/renderer/components/QueueTab.jsx +116 -0
  53. package/src/renderer/components/RequestsListWrapper.jsx +78 -0
  54. package/src/renderer/components/ServerTab.jsx +472 -0
  55. package/src/renderer/components/SongInfoBarWrapper.jsx +77 -0
  56. package/src/renderer/components/StatusBar.jsx +92 -0
  57. package/src/renderer/components/TabNavigation.jsx +77 -0
  58. package/src/renderer/components/TransportControlsWrapper.jsx +69 -0
  59. package/src/renderer/components/creator/CreateTab.jsx +1236 -0
  60. package/src/renderer/dist/assets/kaiPlayer-CoMx__a_.js +2 -0
  61. package/src/renderer/dist/assets/kaiPlayer-CoMx__a_.js.map +1 -0
  62. package/src/renderer/dist/assets/microphoneEngine-BaCUhhQc.js +2 -0
  63. package/src/renderer/dist/assets/microphoneEngine-BaCUhhQc.js.map +1 -0
  64. package/src/renderer/dist/assets/player-DVrqp7N5.js +3 -0
  65. package/src/renderer/dist/assets/player-DVrqp7N5.js.map +1 -0
  66. package/src/renderer/dist/assets/songLoaders-BaTgGib4.js +2 -0
  67. package/src/renderer/dist/assets/songLoaders-BaTgGib4.js.map +1 -0
  68. package/src/renderer/dist/assets/webrtcManager-BhCHWceK.js +2 -0
  69. package/src/renderer/dist/assets/webrtcManager-BhCHWceK.js.map +1 -0
  70. package/src/renderer/dist/js/autoTuneWorklet.js +224 -0
  71. package/src/renderer/dist/js/micPitchDetectorWorklet.js +137 -0
  72. package/src/renderer/dist/js/musicAnalysisWorklet.js +216 -0
  73. package/src/renderer/dist/js/phaseVocoderWorklet.js +341 -0
  74. package/src/renderer/dist/js/soundtouch-worklet.js +1395 -0
  75. package/src/renderer/dist/renderer.css +1 -0
  76. package/src/renderer/dist/renderer.js +62 -0
  77. package/src/renderer/dist/renderer.js.map +1 -0
  78. package/src/renderer/dist/renderer.woff2 +0 -0
  79. package/src/renderer/hooks/useKeyboardShortcuts.js +154 -0
  80. package/src/renderer/index.html +24 -0
  81. package/src/renderer/index.html.backup +372 -0
  82. package/src/renderer/js/PlayerInterface.js +267 -0
  83. package/src/renderer/js/autoTuneWorklet.js +224 -0
  84. package/src/renderer/js/butterchurnVerify.js +46 -0
  85. package/src/renderer/js/canvas-app.js +114 -0
  86. package/src/renderer/js/cdgPlayer.js +685 -0
  87. package/src/renderer/js/kaiPlayer.js +1200 -0
  88. package/src/renderer/js/karaokeRenderer.js +3392 -0
  89. package/src/renderer/js/micPitchDetectorWorklet.js +137 -0
  90. package/src/renderer/js/microphoneEngine.js +656 -0
  91. package/src/renderer/js/musicAnalysisWorklet.js +216 -0
  92. package/src/renderer/js/phaseVocoderWorklet.js +341 -0
  93. package/src/renderer/js/player.js +232 -0
  94. package/src/renderer/js/referencePitchTracker.js +130 -0
  95. package/src/renderer/js/songLoaders.js +334 -0
  96. package/src/renderer/js/soundtouch-worklet.js +1395 -0
  97. package/src/renderer/js/webrtcManager.js +511 -0
  98. package/src/renderer/lib/butterchurn.min.js +6739 -0
  99. package/src/renderer/lib/butterchurnPresets.min.js +1 -0
  100. package/src/renderer/lib/cdgraphics-wrapper.js +16 -0
  101. package/src/renderer/lib/cdgraphics.js +299 -0
  102. package/src/renderer/public/js/autoTuneWorklet.js +224 -0
  103. package/src/renderer/public/js/micPitchDetectorWorklet.js +137 -0
  104. package/src/renderer/public/js/musicAnalysisWorklet.js +216 -0
  105. package/src/renderer/public/js/phaseVocoderWorklet.js +341 -0
  106. package/src/renderer/public/js/soundtouch-worklet.js +1395 -0
  107. package/src/renderer/react-entry.jsx +44 -0
  108. package/src/renderer/styles/tailwind.css +106 -0
  109. package/src/renderer/utils/qrCodeGenerator.js +98 -0
  110. package/src/renderer/vite.config.js +31 -0
  111. package/src/shared/adapters/BridgeInterface.js +195 -0
  112. package/src/shared/components/EffectsPanel.jsx +177 -0
  113. package/src/shared/components/LibraryPanel.jsx +701 -0
  114. package/src/shared/components/LineDetailCanvas.jsx +167 -0
  115. package/src/shared/components/LyricLine.jsx +505 -0
  116. package/src/shared/components/LyricRejection.jsx +84 -0
  117. package/src/shared/components/LyricSuggestion.jsx +80 -0
  118. package/src/shared/components/LyricsEditorCanvas.jsx +271 -0
  119. package/src/shared/components/MixerPanel.jsx +94 -0
  120. package/src/shared/components/PlayerControls.jsx +206 -0
  121. package/src/shared/components/PortalSelect.jsx +239 -0
  122. package/src/shared/components/QueueList.jsx +365 -0
  123. package/src/shared/components/QuickSearch.jsx +126 -0
  124. package/src/shared/components/RequestsList.jsx +121 -0
  125. package/src/shared/components/SongEditor.jsx +1362 -0
  126. package/src/shared/components/SongInfoBar.jsx +81 -0
  127. package/src/shared/components/ThemeToggle.jsx +106 -0
  128. package/src/shared/components/Toast.jsx +30 -0
  129. package/src/shared/components/VisualizationSettings.jsx +243 -0
  130. package/src/shared/constants.js +95 -0
  131. package/src/shared/context/BridgeContext.jsx +32 -0
  132. package/src/shared/contexts/AudioContext.jsx +37 -0
  133. package/src/shared/contexts/PlayerContext.jsx +66 -0
  134. package/src/shared/contexts/SettingsContext.jsx +50 -0
  135. package/src/shared/defaults.js +158 -0
  136. package/src/shared/formatUtils.js +59 -0
  137. package/src/shared/formatUtils.test.js +207 -0
  138. package/src/shared/hooks/useAppState.js +97 -0
  139. package/src/shared/hooks/useAudioEngine.js +264 -0
  140. package/src/shared/hooks/usePlayer.js +89 -0
  141. package/src/shared/hooks/useSettingsPersistence.js +74 -0
  142. package/src/shared/hooks/useWebRTC.js +118 -0
  143. package/src/shared/ipcContracts.js +299 -0
  144. package/src/shared/package.json +3 -0
  145. package/src/shared/services/creatorService.js +373 -0
  146. package/src/shared/services/creatorService.test.js +413 -0
  147. package/src/shared/services/editorService.js +213 -0
  148. package/src/shared/services/editorService.test.js +219 -0
  149. package/src/shared/services/effectsService.js +271 -0
  150. package/src/shared/services/effectsService.test.js +418 -0
  151. package/src/shared/services/libraryService.js +438 -0
  152. package/src/shared/services/libraryService.test.js +474 -0
  153. package/src/shared/services/mixerService.js +172 -0
  154. package/src/shared/services/mixerService.test.js +399 -0
  155. package/src/shared/services/playerService.js +221 -0
  156. package/src/shared/services/playerService.test.js +357 -0
  157. package/src/shared/services/preferencesService.js +219 -0
  158. package/src/shared/services/queueService.js +226 -0
  159. package/src/shared/services/queueService.test.js +430 -0
  160. package/src/shared/services/requestsService.js +155 -0
  161. package/src/shared/services/requestsService.test.js +362 -0
  162. package/src/shared/services/serverSettingsService.js +151 -0
  163. package/src/shared/services/settingsService.js +257 -0
  164. package/src/shared/services/settingsService.test.js +295 -0
  165. package/src/shared/state/StateManager.js +263 -0
  166. package/src/shared/utils/audio.js +42 -0
  167. package/src/shared/utils/format.js +32 -0
  168. package/src/shared/utils/lyricsUtils.js +162 -0
  169. package/src/test/setup.js +40 -0
  170. package/src/utils/cdgLoader.js +180 -0
  171. package/src/utils/m4aLoader.js +333 -0
  172. package/src/web/App.jsx +578 -0
  173. package/src/web/adapters/WebBridge.js +428 -0
  174. package/src/web/components/PlayerSettingsPanel.jsx +231 -0
  175. package/src/web/components/SongSearch.jsx +180 -0
  176. package/src/web/dist/assets/index-0H-RnRrV.js +51 -0
  177. package/src/web/dist/assets/index-0H-RnRrV.js.map +1 -0
  178. package/src/web/dist/assets/index-DYW2zB0u.css +1 -0
  179. package/src/web/dist/index.html +15 -0
  180. package/src/web/index.html +14 -0
  181. package/src/web/main.jsx +10 -0
  182. package/src/web/package-lock.json +1765 -0
  183. package/src/web/pages/SongRequestPage.jsx +619 -0
  184. package/src/web/styles/tailwind.css +68 -0
  185. package/src/web/vite.config.js +27 -0
  186. package/static/fonts/material-icons.woff2 +0 -0
  187. package/static/images/butterchurn-screenshots/Aderrasi - Potion of Spirits.png +0 -0
  188. package/static/images/butterchurn-screenshots/Aderrasi - Songflower _Moss Posy_.png +0 -0
  189. package/static/images/butterchurn-screenshots/Aderrasi - Storm of the Eye _Thunder_ - mash0000 - quasi pseudo meta concentrics.png +0 -0
  190. package/static/images/butterchurn-screenshots/Aderrasi _ Geiss - Airhandler _Kali Mix_ - Canvas Mix.png +0 -0
  191. package/static/images/butterchurn-screenshots/An AdamFX n Martin Infusion 2 flexi - Why The Sky Looks Diffrent Today - AdamFx n Martin Infusion - Tack Tile Disfunction B.png +0 -0
  192. package/static/images/butterchurn-screenshots/Cope - The Neverending Explosion of Red Liquid Fire.png +0 -0
  193. proton lights __Krash_s beat code_ _Phat_remix02b.png +0 -0
  194. package/static/images/butterchurn-screenshots/Eo_S_ _ Phat - cubetrace - v2.png +0 -0
  195. package/static/images/butterchurn-screenshots/Eo_S_ _ Zylot - skylight _Stained Glass Majesty mix_.png +0 -0
  196. package/static/images/butterchurn-screenshots/Flexi - alien fish pond.png +0 -0
  197. package/static/images/butterchurn-screenshots/Flexi - area 51.png +0 -0
  198. package/static/images/butterchurn-screenshots/Flexi - infused with the spiral.png +0 -0
  199. package/static/images/butterchurn-screenshots/Flexi - mindblob _shiny mix_.png +0 -0
  200. package/static/images/butterchurn-screenshots/Flexi - mindblob mix.png +0 -0
  201. package/static/images/butterchurn-screenshots/Flexi - predator-prey-spirals.png +0 -0
  202. package/static/images/butterchurn-screenshots/Flexi - smashing fractals _acid etching mix_.png +0 -0
  203. package/static/images/butterchurn-screenshots/Flexi - truly soft piece of software - this is generic texturing _Jelly_ .png +0 -0
  204. package/static/images/butterchurn-screenshots/Flexi _ Martin - astral projection.png +0 -0
  205. package/static/images/butterchurn-screenshots/Flexi _ Martin - cascading decay swing.png +0 -0
  206. package/static/images/butterchurn-screenshots/Flexi _ amandio c - piercing 05 - Kopie _2_ - Kopie.png +0 -0
  207. package/static/images/butterchurn-screenshots/Flexi _ stahlregen - jelly showoff parade.png +0 -0
  208. package/static/images/butterchurn-screenshots/Flexi_ fishbrain_ Geiss _ Martin - tokamak witchery.png +0 -0
  209. package/static/images/butterchurn-screenshots/Flexi_ martin _ geiss - dedicated to the sherwin maxawow.png +0 -0
  210. package/static/images/butterchurn-screenshots/Fumbling_Foo _ Flexi_ Martin_ Orb_ Unchained - Star Nova v7b.png +0 -0
  211. package/static/images/butterchurn-screenshots/Geiss - Cauldron - painterly 2 _saturation remix_.png +0 -0
  212. package/static/images/butterchurn-screenshots/Geiss - Reaction Diffusion 2.png +0 -0
  213. package/static/images/butterchurn-screenshots/Geiss - Spiral Artifact.png +0 -0
  214. package/static/images/butterchurn-screenshots/Geiss - Thumb Drum.png +0 -0
  215. package/static/images/butterchurn-screenshots/Geiss _ Flexi _ Martin - disconnected.png +0 -0
  216. package/static/images/butterchurn-screenshots/Geiss_ Flexi _ Stahlregen - Thumbdrum Tokamak _crossfiring aftermath jelly mashup_.png +0 -0
  217. package/static/images/butterchurn-screenshots/Goody - The Wild Vort.png +0 -0
  218. package/static/images/butterchurn-screenshots/Idiot - Star Of Annon.png +0 -0
  219. package/static/images/butterchurn-screenshots/Krash _ Illusion - Spiral Movement.png +0 -0
  220. package/static/images/butterchurn-screenshots/Martin - QBikal - Surface Turbulence IIb.png +0 -0
  221. package/static/images/butterchurn-screenshots/Martin - acid wiring.png +0 -0
  222. package/static/images/butterchurn-screenshots/Martin - charisma.png +0 -0
  223. package/static/images/butterchurn-screenshots/Martin - liquid arrows.png +0 -0
  224. package/static/images/butterchurn-screenshots/Milk Artist At our Best - FED - SlowFast Ft AdamFX n Martin - HD CosmoFX.png +0 -0
  225. package/static/images/butterchurn-screenshots/ORB - Waaa.png +0 -0
  226. package/static/images/butterchurn-screenshots/Phat_fiShbRaiN_Eo_S_Mandala_Chasers_remix.png +0 -0
  227. package/static/images/butterchurn-screenshots/Rovastar - Oozing Resistance.png +0 -0
  228. package/static/images/butterchurn-screenshots/Rovastar _ Loadus _ Geiss - FractalDrop _Triple Mix_.png +0 -0
  229. package/static/images/butterchurn-screenshots/TonyMilkdrop - Leonardo Da Vinci_s Balloon _Flexi - merry-go-round _ techstyle_.png +0 -0
  230. package/static/images/butterchurn-screenshots/TonyMilkdrop - Magellan_s Nebula _Flexi - you enter first _ multiverse_.png +0 -0
  231. package/static/images/butterchurn-screenshots/Unchained - Rewop.png +0 -0
  232. package/static/images/butterchurn-screenshots/Unchained - Unified Drag 2.png +0 -0
  233. package/static/images/butterchurn-screenshots/Unchained _ Rovastar - Wormhole Pillars _Hall of Shadows mix_.png +0 -0
  234. package/static/images/butterchurn-screenshots/Zylot - Paint Spill _Music Reactive Paint Mix_.png +0 -0
  235. package/static/images/butterchurn-screenshots/Zylot - Star Ornament.png +0 -0
  236. package/static/images/butterchurn-screenshots/Zylot - True Visionary _Final Mix_.png +0 -0
  237. package/static/images/butterchurn-screenshots/_Aderrasi - Wanderer in Curved Space - mash0000 - faclempt kibitzing meshuggana schmaltz _Geiss color mix_.png +0 -0
  238. package/static/images/butterchurn-screenshots/_Geiss - Artifact 01.png +0 -0
  239. package/static/images/butterchurn-screenshots/_Geiss - Desert Rose 2.png +0 -0
  240. package/static/images/butterchurn-screenshots/_Geiss - untitled.png +0 -0
  241. package/static/images/butterchurn-screenshots/_Mig_049.png +0 -0
  242. package/static/images/butterchurn-screenshots/_Mig_085.png +0 -0
  243. package/static/images/butterchurn-screenshots/_Rovastar _ Geiss - Hurricane Nightmare _Posterize Mix_.png +0 -0
  244. package/static/images/butterchurn-screenshots/___ Royal - Mashup _197_.png +0 -0
  245. package/static/images/butterchurn-screenshots/___ Royal - Mashup _220_.png +0 -0
  246. package/static/images/butterchurn-screenshots/___ Royal - Mashup _431_.png +0 -0
  247. package/static/images/butterchurn-screenshots/cope _ martin - mother-of-pearl.png +0 -0
  248. package/static/images/butterchurn-screenshots/fiShbRaiN _ Flexi - witchcraft 2_0.png +0 -0
  249. package/static/images/butterchurn-screenshots/flexi - bouncing balls _double mindblob neon mix_.png +0 -0
  250. package/static/images/butterchurn-screenshots/flexi - mom_ why the sky looks different today.png +0 -0
  251. package/static/images/butterchurn-screenshots/flexi - patternton_ district of media_ capitol of the united abstractions of fractopia.png +0 -0
  252. package/static/images/butterchurn-screenshots/flexi - swing out on the spiral.png +0 -0
  253. package/static/images/butterchurn-screenshots/flexi - what is the matrix.png +0 -0
  254. package/static/images/butterchurn-screenshots/flexi _ amandio c - organic _random mashup_.png +0 -0
  255. package/static/images/butterchurn-screenshots/flexi _ amandio c - organic12-3d-2_milk.png +0 -0
  256. package/static/images/butterchurn-screenshots/flexi _ fishbrain - neon mindblob grafitti.png +0 -0
  257. package/static/images/butterchurn-screenshots/flexi _ geiss - pogo cubes vs_ tokamak vs_ game of life _stahls jelly 4_5 finish_.png +0 -0
  258. package/static/images/butterchurn-screenshots/high-altitude basket unraveling - singh grooves nitrogen argon nz_.png +0 -0
  259. package/static/images/butterchurn-screenshots/martin - The Bridge of Khazad-Dum.png +0 -0
  260. package/static/images/butterchurn-screenshots/martin - angel flight.png +0 -0
  261. package/static/images/butterchurn-screenshots/martin - another kind of groove.png +0 -0
  262. package/static/images/butterchurn-screenshots/martin - bombyx mori.png +0 -0
  263. package/static/images/butterchurn-screenshots/martin - castle in the air.png +0 -0
  264. package/static/images/butterchurn-screenshots/martin - chain breaker.png +0 -0
  265. package/static/images/butterchurn-screenshots/martin - disco mix 4.png +0 -0
  266. package/static/images/butterchurn-screenshots/martin - extreme heat.png +0 -0
  267. package/static/images/butterchurn-screenshots/martin - frosty caves 2.png +0 -0
  268. package/static/images/butterchurn-screenshots/martin - fruit machine.png +0 -0
  269. package/static/images/butterchurn-screenshots/martin - ghost city.png +0 -0
  270. package/static/images/butterchurn-screenshots/martin - glass corridor.png +0 -0
  271. package/static/images/butterchurn-screenshots/martin - infinity _2010 update_.png +0 -0
  272. package/static/images/butterchurn-screenshots/martin - mandelbox explorer - high speed demo version.png +0 -0
  273. package/static/images/butterchurn-screenshots/martin - mucus cervix.png +0 -0
  274. package/static/images/butterchurn-screenshots/martin - reflections on black tiles.png +0 -0
  275. package/static/images/butterchurn-screenshots/martin - stormy sea _2010 update_.png +0 -0
  276. package/static/images/butterchurn-screenshots/martin - witchcraft reloaded.png +0 -0
  277. package/static/images/butterchurn-screenshots/martin _ flexi - diamond cutter _prismaticvortex_com_ - camille - i wish i wish i wish i was constrained.png +0 -0
  278. package/static/images/butterchurn-screenshots/martin _shadow harlequins shape code_ - fata morgana.png +0 -0
  279. package/static/images/butterchurn-screenshots/martin_ flexi_ fishbrain _ sto - enterstate _random mashup_.png +0 -0
  280. package/static/images/butterchurn-screenshots/sawtooth grin roam.png +0 -0
  281. package/static/images/butterchurn-screenshots/shifter - dark tides bdrv mix 2.png +0 -0
  282. package/static/images/butterchurn-screenshots/suksma - Rovastar - Sunflower Passion _Enlightment Mix__Phat_edit _ flexi und martin shaders - circumflex in character classes in regular expression.png +0 -0
  283. package/static/images/butterchurn-screenshots/suksma - heretical crosscut playpen.png +0 -0
  284. package/static/images/butterchurn-screenshots/suksma - uninitialized variabowl _hydroponic chronic_.png +0 -0
  285. package/static/images/butterchurn-screenshots/suksma - vector exp 1 - couldn_t not.png +0 -0
  286. package/static/images/butterchurn-screenshots/yin - 191 - Temporal singularities.png +0 -0
  287. package/static/images/logo-512.png +0 -0
  288. package/static/images/logo.png +0 -0
  289. package/static/loukai-logo.png +0 -0
  290. package/static/screenshot-generator.html +610 -0
@@ -0,0 +1,438 @@
1
+ /**
2
+ * Library Service - Shared business logic for library management
3
+ *
4
+ * Used by both IPC handlers (Electron) and REST endpoints (Web Server)
5
+ * to ensure consistent library behavior across all interfaces.
6
+ */
7
+
8
+ /**
9
+ * Get the current songs folder path
10
+ * @param {Object} mainApp - Main application instance with settings
11
+ * @returns {Object} Result with success status and folder path
12
+ */
13
+ export function getSongsFolder(mainApp) {
14
+ try {
15
+ const folder = mainApp.settings?.getSongsFolder?.();
16
+ return {
17
+ success: true,
18
+ folder: folder || null,
19
+ };
20
+ } catch (error) {
21
+ return {
22
+ success: false,
23
+ error: error.message,
24
+ };
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Get cached library songs
30
+ * @param {Object} mainApp - Main application instance
31
+ * @returns {Object} Result with success status and cached files
32
+ */
33
+ export function getCachedSongs(mainApp) {
34
+ if (mainApp.cachedLibrary) {
35
+ return {
36
+ success: true,
37
+ files: mainApp.cachedLibrary,
38
+ cached: true,
39
+ };
40
+ }
41
+
42
+ return {
43
+ success: true,
44
+ files: [],
45
+ cached: false,
46
+ };
47
+ }
48
+
49
+ /**
50
+ * Get library songs (from cache or by scanning)
51
+ * @param {Object} mainApp - Main application instance
52
+ * @returns {Promise<Object>} Result with success status and songs array
53
+ */
54
+ export async function getLibrarySongs(mainApp) {
55
+ try {
56
+ // Return cached library if available
57
+ if (mainApp.cachedLibrary && mainApp.cachedLibrary.length > 0) {
58
+ return {
59
+ success: true,
60
+ songs: mainApp.cachedLibrary,
61
+ fromCache: true,
62
+ };
63
+ }
64
+
65
+ // Otherwise scan
66
+ const songsFolder = mainApp.settings?.getSongsFolder?.();
67
+ if (!songsFolder) {
68
+ return {
69
+ success: false,
70
+ error: 'Songs folder not set',
71
+ songs: [],
72
+ };
73
+ }
74
+
75
+ const files = await mainApp.scanForKaiFiles(songsFolder);
76
+
77
+ return {
78
+ success: true,
79
+ songs: files,
80
+ fromCache: false,
81
+ };
82
+ } catch (error) {
83
+ console.error('Error getting library songs:', error);
84
+ return {
85
+ success: false,
86
+ error: error.message,
87
+ songs: [],
88
+ };
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Scan library folder and cache results
94
+ * @param {Object} mainApp - Main application instance
95
+ * @param {Function} [progressCallback] - Optional callback for progress updates (current, total)
96
+ * @returns {Promise<Object>} Result with success status, files, and cache info
97
+ */
98
+ export async function scanLibrary(mainApp, progressCallback) {
99
+ try {
100
+ const songsFolder = mainApp.settings?.getSongsFolder?.();
101
+ if (!songsFolder) {
102
+ return {
103
+ success: false,
104
+ error: 'Songs folder not set',
105
+ };
106
+ }
107
+
108
+ // Get total file count for progress
109
+ const allFiles = (await mainApp.findAllKaiFiles?.(songsFolder)) || [];
110
+ const totalFiles = allFiles.length;
111
+
112
+ if (progressCallback) {
113
+ progressCallback({ current: 0, total: totalFiles });
114
+ }
115
+
116
+ // Scan with progress
117
+ const files =
118
+ (await mainApp.scanForKaiFilesWithProgress?.(songsFolder, totalFiles, progressCallback)) ||
119
+ [];
120
+
121
+ // Cache the results
122
+ mainApp.cachedLibrary = files;
123
+
124
+ if (progressCallback) {
125
+ progressCallback({ current: totalFiles, total: totalFiles });
126
+ }
127
+
128
+ return {
129
+ success: true,
130
+ files,
131
+ count: files.length,
132
+ cached: true,
133
+ };
134
+ } catch (error) {
135
+ console.error('❌ Failed to scan library:', error);
136
+ return {
137
+ success: false,
138
+ error: error.message,
139
+ };
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Sync library (incremental update - only scans new/modified files)
145
+ * @param {Object} mainApp - Main application instance
146
+ * @param {Function} [progressCallback] - Optional callback for progress updates
147
+ * @returns {Promise<Object>} Result with success status and updated files
148
+ */
149
+ export async function syncLibrary(mainApp, progressCallback) {
150
+ try {
151
+ const songsFolder = mainApp.settings?.getSongsFolder?.();
152
+ if (!songsFolder) {
153
+ return {
154
+ success: false,
155
+ error: 'Songs folder not set',
156
+ };
157
+ }
158
+
159
+ // Step 1: Load cached library from mainApp or disk
160
+ let cachedFiles = [];
161
+ if (mainApp.cachedLibrary && mainApp.cachedLibrary.length > 0) {
162
+ cachedFiles = mainApp.cachedLibrary;
163
+ }
164
+
165
+ // Step 2: Quick filesystem scan to find all valid files (no metadata parsing)
166
+ console.log('🔍 Scanning filesystem...');
167
+ const filesystemScan = (await mainApp.scanFilesystemForSync?.(songsFolder)) || [];
168
+ const totalFiles = filesystemScan.length;
169
+
170
+ if (progressCallback) {
171
+ progressCallback({ current: Math.floor(totalFiles * 0.1), total: totalFiles });
172
+ }
173
+
174
+ // Build a map of current filesystem state (keyed by primary file path)
175
+ const currentFilesMap = new Map();
176
+ for (const item of filesystemScan) {
177
+ currentFilesMap.set(item.path, item);
178
+ }
179
+
180
+ // Step 3: Check cached files to see which ones are still valid
181
+ const stillValid = [];
182
+ const removedPaths = [];
183
+
184
+ for (const cachedFile of cachedFiles) {
185
+ const filePath = cachedFile.file || cachedFile.path;
186
+ const fsItem = currentFilesMap.get(filePath);
187
+
188
+ if (fsItem) {
189
+ // File still exists in filesystem with correct pairing
190
+ stillValid.push(cachedFile);
191
+ currentFilesMap.delete(filePath); // Mark as processed
192
+ } else {
193
+ // File is gone or invalid
194
+ removedPaths.push(filePath);
195
+ }
196
+ }
197
+
198
+ // Step 4: Remaining items in currentFilesMap are NEW files that need metadata parsing
199
+ const newFiles = Array.from(currentFilesMap.values());
200
+
201
+ console.log(
202
+ `🔄 Sync: ${newFiles.length} new, ${removedPaths.length} removed, ${totalFiles} total`
203
+ );
204
+
205
+ // Start with files that are still valid (already have metadata)
206
+ let updatedFiles = stillValid;
207
+
208
+ // Step 5: Process new files (10-100% progress)
209
+ if (newFiles.length > 0) {
210
+ const newFilesData =
211
+ (await mainApp.parseMetadataWithProgress?.(newFiles, totalFiles, 0.1)) || [];
212
+ updatedFiles = updatedFiles.concat(newFilesData);
213
+ } else {
214
+ // No new files, go straight to 100%
215
+ if (progressCallback) {
216
+ progressCallback({ current: totalFiles, total: totalFiles });
217
+ }
218
+ }
219
+
220
+ // Update cache
221
+ mainApp.cachedLibrary = updatedFiles;
222
+
223
+ return {
224
+ success: true,
225
+ files: updatedFiles,
226
+ count: updatedFiles.length,
227
+ added: newFiles.length,
228
+ removed: removedPaths.length,
229
+ removedPaths,
230
+ };
231
+ } catch (error) {
232
+ console.error('❌ Failed to sync library:', error);
233
+ return {
234
+ success: false,
235
+ error: error.message,
236
+ };
237
+ }
238
+ }
239
+
240
+ /**
241
+ * Search songs in the library
242
+ * @param {Object} mainApp - Main application instance
243
+ * @param {string} query - Search query
244
+ * @returns {Object} Result with success status and matching songs
245
+ */
246
+ export function searchSongs(mainApp, query) {
247
+ try {
248
+ if (!query || !query.trim()) {
249
+ return {
250
+ success: true,
251
+ songs: [],
252
+ };
253
+ }
254
+
255
+ const cachedSongs = mainApp.cachedLibrary || [];
256
+ if (cachedSongs.length === 0) {
257
+ return {
258
+ success: true,
259
+ songs: [],
260
+ };
261
+ }
262
+
263
+ const searchLower = query.toLowerCase().trim();
264
+ const matches = cachedSongs
265
+ .filter(
266
+ (song) =>
267
+ song.title?.toLowerCase().includes(searchLower) ||
268
+ song.artist?.toLowerCase().includes(searchLower) ||
269
+ song.album?.toLowerCase().includes(searchLower)
270
+ )
271
+ .sort((a, b) => {
272
+ // Prioritize title matches over artist/album matches
273
+ const aTitleMatch = a.title?.toLowerCase().includes(searchLower);
274
+ const bTitleMatch = b.title?.toLowerCase().includes(searchLower);
275
+ if (aTitleMatch && !bTitleMatch) return -1;
276
+ if (!aTitleMatch && bTitleMatch) return 1;
277
+ // Then sort alphabetically by title
278
+ return (a.title || '').localeCompare(b.title || '');
279
+ })
280
+ .slice(0, 50); // Limit to 50 results
281
+
282
+ return {
283
+ success: true,
284
+ songs: matches,
285
+ };
286
+ } catch (error) {
287
+ return {
288
+ success: false,
289
+ error: error.message,
290
+ songs: [],
291
+ };
292
+ }
293
+ }
294
+
295
+ /**
296
+ * Get song info by file path
297
+ * @param {Object} mainApp - Main application instance
298
+ * @param {string} filePath - Path to the song file
299
+ * @returns {Promise<Object>} Result with success status and song info
300
+ */
301
+ export async function getSongInfo(mainApp, filePath) {
302
+ try {
303
+ if (!filePath) {
304
+ return {
305
+ success: false,
306
+ error: 'File path is required',
307
+ };
308
+ }
309
+
310
+ // Check cache first
311
+ const cachedResult = getCachedSongs(mainApp);
312
+ const cachedSong = cachedResult.files?.find((f) => f.path === filePath);
313
+
314
+ if (cachedSong) {
315
+ return {
316
+ success: true,
317
+ song: cachedSong,
318
+ fromCache: true,
319
+ };
320
+ }
321
+
322
+ // Not in cache, extract metadata directly
323
+ const lowerPath = filePath.toLowerCase();
324
+ const format =
325
+ lowerPath.endsWith('.stem.m4a') || lowerPath.endsWith('.m4a') || lowerPath.endsWith('.mp4')
326
+ ? 'm4a-stems'
327
+ : lowerPath.endsWith('.kar') || lowerPath.endsWith('.zip')
328
+ ? 'cdg-archive'
329
+ : 'cdg-pair';
330
+
331
+ let metadata;
332
+ if (format === 'm4a-stems') {
333
+ metadata = await mainApp.extractM4AMetadata?.(filePath);
334
+ } else if (format === 'cdg-archive') {
335
+ metadata = await mainApp.extractCDGArchiveMetadata?.(filePath);
336
+ } else {
337
+ // For CDG pairs, we'd need the CDG path too
338
+ return {
339
+ success: false,
340
+ error: 'CDG pair requires both MP3 and CDG paths',
341
+ };
342
+ }
343
+
344
+ return {
345
+ success: true,
346
+ song: {
347
+ path: filePath,
348
+ format,
349
+ ...metadata,
350
+ },
351
+ fromCache: false,
352
+ };
353
+ } catch (error) {
354
+ console.error('Error getting song info:', error);
355
+ return {
356
+ success: false,
357
+ error: error.message,
358
+ };
359
+ }
360
+ }
361
+
362
+ /**
363
+ * Clear the library cache
364
+ * @param {Object} mainApp - Main application instance
365
+ * @returns {Object} Result with success status
366
+ */
367
+ export function clearLibraryCache(mainApp) {
368
+ mainApp.cachedLibrary = null;
369
+ return {
370
+ success: true,
371
+ message: 'Library cache cleared',
372
+ };
373
+ }
374
+
375
+ /**
376
+ * Update cache after library scan/sync
377
+ * Updates both mainApp cache and webServer cache (if available)
378
+ * @param {Object} mainApp - Main application instance
379
+ * @param {Array} files - Scanned files to cache
380
+ * @returns {Promise<Object>} Result with success status
381
+ */
382
+ export async function updateLibraryCache(mainApp, files) {
383
+ try {
384
+ // Update main app cache
385
+ mainApp.cachedLibrary = files;
386
+
387
+ // Update web server cache if available
388
+ if (mainApp.webServer) {
389
+ mainApp.webServer.cachedSongs = files;
390
+ mainApp.webServer.songsCacheTime = Date.now();
391
+ mainApp.webServer.fuse = null; // Reset Fuse.js - will rebuild on next search
392
+
393
+ // Notify web admin clients via socket
394
+ if (mainApp.webServer.io) {
395
+ mainApp.webServer.io.emit('library-refreshed', {
396
+ count: files.length,
397
+ timestamp: Date.now(),
398
+ });
399
+ }
400
+ }
401
+
402
+ // Save to disk cache (Electron only)
403
+ if (mainApp.settings?.getSongsFolder) {
404
+ const path = await import('path');
405
+ const fsPromises = await import('fs/promises');
406
+ const { app } = await import('electron');
407
+
408
+ const songsFolder = mainApp.settings.getSongsFolder();
409
+ const cacheFile = path.default.join(app.getPath('userData'), 'library-cache.json');
410
+
411
+ try {
412
+ await fsPromises.default.writeFile(
413
+ cacheFile,
414
+ JSON.stringify({
415
+ songsFolder,
416
+ files,
417
+ cachedAt: new Date().toISOString(),
418
+ }),
419
+ 'utf8'
420
+ );
421
+ console.log('💾 Library cache saved to disk');
422
+ } catch (err) {
423
+ console.error('Failed to save library cache to disk:', err);
424
+ }
425
+ }
426
+
427
+ return {
428
+ success: true,
429
+ count: files.length,
430
+ };
431
+ } catch (error) {
432
+ console.error('Failed to update library cache:', error);
433
+ return {
434
+ success: false,
435
+ error: error.message,
436
+ };
437
+ }
438
+ }