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,172 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Whisper Runner - Lyrics transcription for Loukai Creator
4
+
5
+ Usage: python whisper_runner.py '{"input": "path/to/audio.wav", "model": "large-v3-turbo", "initial_prompt": "Song title. vocabulary hints"}'
6
+
7
+ Outputs transcription with word timestamps as JSON to stdout.
8
+ Progress updates are sent to stderr in format: PROGRESS:percent:message
9
+ """
10
+
11
+ import json
12
+ import sys
13
+ import os
14
+
15
+ def progress(percent, message):
16
+ """Send progress update to stderr"""
17
+ print(f"PROGRESS:{percent}:{message}", file=sys.stderr, flush=True)
18
+
19
+ def main():
20
+ if len(sys.argv) < 2:
21
+ print(json.dumps({"error": "Missing arguments"}))
22
+ sys.exit(1)
23
+
24
+ try:
25
+ args = json.loads(sys.argv[1])
26
+ except json.JSONDecodeError as e:
27
+ print(json.dumps({"error": f"Invalid JSON arguments: {e}"}))
28
+ sys.exit(1)
29
+
30
+ input_path = args.get("input")
31
+ model_name = args.get("model", "large-v3-turbo")
32
+ initial_prompt = args.get("initial_prompt")
33
+ language = args.get("language", "en")
34
+
35
+ if not input_path:
36
+ print(json.dumps({"error": "Missing input path"}))
37
+ sys.exit(1)
38
+
39
+ try:
40
+ import torch
41
+ import whisper
42
+ import numpy as np
43
+
44
+ # Detect device
45
+ if torch.cuda.is_available():
46
+ device = "cuda"
47
+ device_name = torch.cuda.get_device_name(0)
48
+ elif hasattr(torch.backends, 'mps') and torch.backends.mps.is_available():
49
+ device = "mps"
50
+ device_name = "Apple Silicon GPU"
51
+ else:
52
+ device = "cpu"
53
+ device_name = "CPU"
54
+
55
+ progress(0, f"Loading Whisper {model_name} on {device_name}")
56
+
57
+ # Load model
58
+ model = whisper.load_model(model_name, device=device)
59
+
60
+ progress(15, "Loading audio for transcription")
61
+
62
+ # Load audio to get duration
63
+ audio = whisper.load_audio(input_path)
64
+ duration = len(audio) / whisper.audio.SAMPLE_RATE
65
+
66
+ progress(20, f"Transcribing {duration:.1f}s of audio...")
67
+
68
+ # Build transcription parameters
69
+ # Note: We don't use word_timestamps because:
70
+ # 1. MPS doesn't support the DTW algorithm (requires float64)
71
+ # 2. Singing has different timing than speech (stretching/compression)
72
+ # 3. LLM corrections break word alignment anyway
73
+ # We use line-level timing and estimate word positions
74
+ transcribe_params = {
75
+ "word_timestamps": False,
76
+ "language": language,
77
+ "task": "transcribe",
78
+ "verbose": True, # Show transcription progress and lyrics in console
79
+ "condition_on_previous_text": False, # Reduces repetition in singing
80
+ "no_speech_threshold": 0.3, # More permissive for singing
81
+ }
82
+
83
+ if initial_prompt:
84
+ transcribe_params["initial_prompt"] = initial_prompt
85
+ progress(22, f"Using vocabulary hints ({len(initial_prompt.split())} words)")
86
+
87
+ # Transcribe - this is the long operation
88
+ # Whisper processes in 30-second chunks internally
89
+ num_chunks = max(1, int(duration / 30))
90
+ if num_chunks > 1:
91
+ progress(25, f"Processing ~{num_chunks} segments...")
92
+
93
+ # Redirect stdout to stderr during transcription so verbose output doesn't interfere with JSON
94
+ old_stdout = sys.stdout
95
+ sys.stdout = sys.stderr
96
+
97
+ result = model.transcribe(audio, **transcribe_params)
98
+
99
+ # Restore stdout
100
+ sys.stdout = old_stdout
101
+
102
+ progress(85, "Extracting line timestamps")
103
+
104
+ # Extract line-level timestamps and estimate word positions
105
+ words = []
106
+ lines = []
107
+
108
+ for segment in result.get("segments", []):
109
+ segment_text = segment["text"].strip()
110
+ segment_start = segment["start"]
111
+ segment_end = segment["end"]
112
+ segment_duration = segment_end - segment_start
113
+
114
+ # Estimate word timings by evenly distributing across segment
115
+ text_words = segment_text.split()
116
+ segment_words = []
117
+
118
+ if text_words:
119
+ word_duration = segment_duration / len(text_words)
120
+ for i, word_text in enumerate(text_words):
121
+ word_start = segment_start + (i * word_duration)
122
+ word_end = word_start + word_duration
123
+ word_data = {
124
+ "word": word_text,
125
+ "start": round(word_start, 3),
126
+ "end": round(word_end, 3)
127
+ }
128
+ segment_words.append(word_data)
129
+ words.append({
130
+ **word_data,
131
+ "probability": 0.9 # Good confidence in text, estimated timing
132
+ })
133
+
134
+ if segment_words:
135
+ lines.append({
136
+ "text": segment_text,
137
+ "start": round(segment_start, 3),
138
+ "end": round(segment_end, 3),
139
+ "words": segment_words
140
+ })
141
+
142
+ progress(95, f"Organized into {len(lines)} lines")
143
+
144
+ # Calculate some stats for the UI
145
+ avg_confidence = sum(w["probability"] for w in words) / len(words) if words else 0
146
+
147
+ progress(100, f"✓ Transcribed {len(words)} words, {len(lines)} lines")
148
+
149
+ # Output result
150
+ output = {
151
+ "success": True,
152
+ "text": result.get("text", "").strip(),
153
+ "language": result.get("language", language),
154
+ "lines": lines,
155
+ "words": words,
156
+ "model": model_name,
157
+ "device": device,
158
+ "duration": duration,
159
+ "avgConfidence": round(avg_confidence, 3)
160
+ }
161
+ print(json.dumps(output))
162
+
163
+ except Exception as e:
164
+ import traceback
165
+ print(json.dumps({
166
+ "error": str(e),
167
+ "traceback": traceback.format_exc()
168
+ }))
169
+ sys.exit(1)
170
+
171
+ if __name__ == "__main__":
172
+ main()
@@ -0,0 +1,268 @@
1
+ /**
2
+ * Python Runner - Spawns Python ML scripts from Node.js
3
+ *
4
+ * Handles:
5
+ * - Demucs stem separation
6
+ * - Whisper transcription
7
+ * - CREPE pitch detection
8
+ *
9
+ * Progress is parsed from stderr and emitted via callbacks.
10
+ * Results are parsed from stdout JSON.
11
+ */
12
+
13
+ import { spawn } from 'child_process';
14
+ import { join, dirname } from 'path';
15
+ import { fileURLToPath } from 'url';
16
+ import { getPythonPath, getPythonEnv } from './systemChecker.js';
17
+
18
+ const __filename = fileURLToPath(import.meta.url);
19
+ const __dirname = dirname(__filename);
20
+
21
+ // Path to Python scripts - handle unpacked asar for packaged apps
22
+ let PYTHON_SCRIPTS_DIR = join(__dirname, 'python');
23
+
24
+ // When running from asar, scripts are in app.asar.unpacked instead
25
+ if (PYTHON_SCRIPTS_DIR.includes('app.asar')) {
26
+ PYTHON_SCRIPTS_DIR = PYTHON_SCRIPTS_DIR.replace('app.asar', 'app.asar.unpacked');
27
+ }
28
+
29
+ /**
30
+ * Run a Python script with JSON arguments
31
+ *
32
+ * @param {string} scriptName - Name of script (e.g., 'demucs_runner.py')
33
+ * @param {Object} args - Arguments to pass as JSON
34
+ * @param {Function} onProgress - Progress callback (step, message, progress%)
35
+ * @param {Function} onConsoleOutput - Raw console output callback (line)
36
+ * @param {Function} onProcess - Callback to receive the spawned process (for cancellation)
37
+ * @returns {Promise<Object>} Parsed JSON result from script
38
+ */
39
+ export function runPythonScript(
40
+ scriptName,
41
+ args,
42
+ onProgress = null,
43
+ onConsoleOutput = null,
44
+ onProcess = null
45
+ ) {
46
+ return new Promise((resolve, reject) => {
47
+ const pythonPath = getPythonPath();
48
+ const scriptPath = join(PYTHON_SCRIPTS_DIR, scriptName);
49
+ const argsJson = JSON.stringify(args);
50
+
51
+ const proc = spawn(pythonPath, [scriptPath, argsJson], {
52
+ env: getPythonEnv(),
53
+ timeout: 3600000, // 1 hour timeout for long operations
54
+ });
55
+
56
+ // Allow caller to track the process for cancellation
57
+ if (onProcess) {
58
+ onProcess(proc);
59
+ }
60
+
61
+ let stdout = '';
62
+ let stderr = '';
63
+
64
+ proc.stdout.on('data', (data) => {
65
+ stdout += data.toString();
66
+ });
67
+
68
+ // Track last known progress message for tqdm updates
69
+ let lastProgressMessage = '';
70
+ let lastProgressPercent = 0;
71
+
72
+ proc.stderr.on('data', (data) => {
73
+ const text = data.toString();
74
+ stderr += text;
75
+
76
+ const lines = text.split('\n');
77
+
78
+ for (const line of lines) {
79
+ // Emit raw console output
80
+ if (onConsoleOutput && line.trim()) {
81
+ onConsoleOutput(line);
82
+ }
83
+
84
+ // Parse our PROGRESS: updates
85
+ if (line.startsWith('PROGRESS:')) {
86
+ const parts = line.split(':');
87
+ if (parts.length >= 3) {
88
+ lastProgressPercent = parseInt(parts[1], 10);
89
+ lastProgressMessage = parts.slice(2).join(':');
90
+ if (onProgress) {
91
+ onProgress(lastProgressPercent, lastProgressMessage);
92
+ }
93
+ }
94
+ continue;
95
+ }
96
+
97
+ // Parse tqdm progress bars: " 14%|█▍ | 4/28 [00:10<01:02, 2.60s/it]"
98
+ // or simpler: " 4/28" pattern
99
+ const tqdmMatch = line.match(/(\d+)%\|.*?\|\s*(\d+)\/(\d+)/);
100
+ if (tqdmMatch && onProgress) {
101
+ const current = parseInt(tqdmMatch[2], 10);
102
+ const total = parseInt(tqdmMatch[3], 10);
103
+ if (total > 0) {
104
+ // Map tqdm progress (0-100%) to our 10-80% range for separation
105
+ const tqdmPercent = (current / total) * 100;
106
+ const mappedPercent = Math.floor(10 + tqdmPercent * 0.7);
107
+ onProgress(mappedPercent, `${lastProgressMessage} (${current}/${total})`);
108
+ }
109
+ continue;
110
+ }
111
+
112
+ // Also try simpler pattern for tqdm without percentage
113
+ const simpleMatch = line.match(/\s+(\d+)\/(\d+)\s+\[/);
114
+ if (simpleMatch && onProgress) {
115
+ const current = parseInt(simpleMatch[1], 10);
116
+ const total = parseInt(simpleMatch[2], 10);
117
+ if (total > 0) {
118
+ const tqdmPercent = (current / total) * 100;
119
+ const mappedPercent = Math.floor(10 + tqdmPercent * 0.7);
120
+ onProgress(mappedPercent, `${lastProgressMessage} (${current}/${total})`);
121
+ }
122
+ }
123
+ }
124
+ });
125
+
126
+ proc.on('close', (code) => {
127
+ try {
128
+ const result = JSON.parse(stdout.trim());
129
+
130
+ if (result.error) {
131
+ // Include full traceback if available
132
+ const errorMsg = result.traceback
133
+ ? `${result.error}\n\nTraceback:\n${result.traceback}`
134
+ : result.error;
135
+ console.error('❌ Python script error:', errorMsg);
136
+ reject(new Error(errorMsg));
137
+ } else {
138
+ resolve(result);
139
+ }
140
+ } catch (e) {
141
+ if (code !== 0) {
142
+ const errorMsg = `Python script failed (code ${code}):\n${stderr}`;
143
+ console.error('❌ Python script failed:', errorMsg);
144
+ reject(new Error(errorMsg));
145
+ } else {
146
+ const errorMsg = `Failed to parse Python output: ${e.message}\nStderr: ${stderr}\nStdout: ${stdout.slice(-500)}`;
147
+ console.error('❌ Failed to parse Python output:', errorMsg);
148
+ reject(new Error(errorMsg));
149
+ }
150
+ }
151
+ });
152
+
153
+ proc.on('error', (err) => {
154
+ reject(new Error(`Failed to spawn Python: ${err.message}`));
155
+ });
156
+ });
157
+ }
158
+
159
+ /**
160
+ * Run Demucs stem separation
161
+ *
162
+ * @param {string} inputPath - Path to input audio file
163
+ * @param {string} outputDir - Directory to save stems
164
+ * @param {Object} options - Options
165
+ * @param {string} options.model - Demucs model (default 'htdemucs_ft')
166
+ * @param {number} options.numStems - Number of stems (2 or 4)
167
+ * @param {Function} onProgress - Progress callback
168
+ * @param {Function} onConsoleOutput - Raw console output callback
169
+ * @param {Function} onProcess - Process callback for cancellation
170
+ * @returns {Promise<Object>} Result with stem file paths
171
+ */
172
+ export function runDemucs(
173
+ inputPath,
174
+ outputDir,
175
+ options = {},
176
+ onProgress = null,
177
+ onConsoleOutput = null,
178
+ onProcess = null
179
+ ) {
180
+ return runPythonScript(
181
+ 'demucs_runner.py',
182
+ {
183
+ input: inputPath,
184
+ output_dir: outputDir,
185
+ model: options.model || 'htdemucs_ft',
186
+ num_stems: options.numStems || 4,
187
+ },
188
+ onProgress,
189
+ onConsoleOutput,
190
+ onProcess
191
+ );
192
+ }
193
+
194
+ /**
195
+ * Run Whisper transcription
196
+ *
197
+ * @param {string} inputPath - Path to audio file (preferably vocals)
198
+ * @param {Object} options - Options
199
+ * @param {string} options.model - Whisper model (default 'large-v3-turbo')
200
+ * @param {string} options.initialPrompt - Initial prompt with vocabulary hints
201
+ * @param {string} options.language - Language code (default 'en')
202
+ * @param {Function} onProgress - Progress callback
203
+ * @param {Function} onConsoleOutput - Raw console output callback
204
+ * @param {Function} onProcess - Process callback for cancellation
205
+ * @returns {Promise<Object>} Transcription result with word timestamps
206
+ */
207
+ export function runWhisper(
208
+ inputPath,
209
+ options = {},
210
+ onProgress = null,
211
+ onConsoleOutput = null,
212
+ onProcess = null
213
+ ) {
214
+ return runPythonScript(
215
+ 'whisper_runner.py',
216
+ {
217
+ input: inputPath,
218
+ model: options.model || 'large-v3-turbo',
219
+ initial_prompt: options.initialPrompt || null,
220
+ language: options.language || 'en',
221
+ },
222
+ onProgress,
223
+ onConsoleOutput,
224
+ onProcess
225
+ );
226
+ }
227
+
228
+ /**
229
+ * Run CREPE pitch detection
230
+ *
231
+ * @param {string} inputPath - Path to vocal audio file
232
+ * @param {string} outputPath - Path to save pitch data JSON (optional)
233
+ * @param {Object} options - Options
234
+ * @param {string} options.model - CREPE model capacity (default 'full')
235
+ * @param {number} options.hopLength - Hop length in samples (default 512)
236
+ * @param {Function} onProgress - Progress callback
237
+ * @param {Function} onConsoleOutput - Raw console output callback
238
+ * @param {Function} onProcess - Process callback for cancellation
239
+ * @returns {Promise<Object>} Pitch detection result
240
+ */
241
+ export function runCrepe(
242
+ inputPath,
243
+ outputPath = null,
244
+ options = {},
245
+ onProgress = null,
246
+ onConsoleOutput = null,
247
+ onProcess = null
248
+ ) {
249
+ return runPythonScript(
250
+ 'crepe_runner.py',
251
+ {
252
+ input: inputPath,
253
+ output: outputPath,
254
+ model: options.model || 'tiny',
255
+ hop_length: options.hopLength || 512,
256
+ },
257
+ onProgress,
258
+ onConsoleOutput,
259
+ onProcess
260
+ );
261
+ }
262
+
263
+ export default {
264
+ runPythonScript,
265
+ runDemucs,
266
+ runWhisper,
267
+ runCrepe,
268
+ };