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.
- package/README.md +558 -0
- package/bin/loukai.js +32 -0
- package/package.json +243 -0
- package/src/main/appState.js +250 -0
- package/src/main/audioEngine.js +478 -0
- package/src/main/creator/conversionService.js +503 -0
- package/src/main/creator/downloadManager.js +1128 -0
- package/src/main/creator/ffmpegService.js +487 -0
- package/src/main/creator/installLogger.js +51 -0
- package/src/main/creator/keyDetection.js +212 -0
- package/src/main/creator/llmService.js +370 -0
- package/src/main/creator/lrclibService.js +340 -0
- package/src/main/creator/python/crepe_runner.py +189 -0
- package/src/main/creator/python/demucs_runner.py +158 -0
- package/src/main/creator/python/whisper_runner.py +172 -0
- package/src/main/creator/pythonRunner.js +268 -0
- package/src/main/creator/stemBuilder.js +491 -0
- package/src/main/creator/systemChecker.js +474 -0
- package/src/main/handlers/appHandlers.js +45 -0
- package/src/main/handlers/audioHandlers.js +33 -0
- package/src/main/handlers/autotuneHandlers.js +28 -0
- package/src/main/handlers/canvasHandlers.js +84 -0
- package/src/main/handlers/creatorHandlers.js +159 -0
- package/src/main/handlers/editorHandlers.js +98 -0
- package/src/main/handlers/effectsHandlers.js +100 -0
- package/src/main/handlers/fileHandlers.js +45 -0
- package/src/main/handlers/index.js +78 -0
- package/src/main/handlers/libraryHandlers.js +96 -0
- package/src/main/handlers/mixerHandlers.js +64 -0
- package/src/main/handlers/playerHandlers.js +39 -0
- package/src/main/handlers/preferencesHandlers.js +46 -0
- package/src/main/handlers/queueHandlers.js +81 -0
- package/src/main/handlers/rendererHandlers.js +63 -0
- package/src/main/handlers/settingsHandlers.js +42 -0
- package/src/main/handlers/webServerHandlers.js +105 -0
- package/src/main/main.js +2351 -0
- package/src/main/preload.js +252 -0
- package/src/main/settingsManager.js +139 -0
- package/src/main/statePersistence.js +193 -0
- package/src/main/utils/pathValidator.js +112 -0
- package/src/main/webServer.js +2535 -0
- package/src/native/autotune.js +417 -0
- package/src/renderer/adapters/ElectronBridge.js +677 -0
- package/src/renderer/canvas.html +80 -0
- package/src/renderer/components/App.jsx +303 -0
- package/src/renderer/components/AppRoot.jsx +37 -0
- package/src/renderer/components/AudioDeviceSettings.jsx +145 -0
- package/src/renderer/components/EffectsPanelWrapper.jsx +267 -0
- package/src/renderer/components/MixerTab.jsx +233 -0
- package/src/renderer/components/MixerTabWrapper.jsx +31 -0
- package/src/renderer/components/PortalSelect.jsx +239 -0
- package/src/renderer/components/QueueTab.jsx +116 -0
- package/src/renderer/components/RequestsListWrapper.jsx +78 -0
- package/src/renderer/components/ServerTab.jsx +472 -0
- package/src/renderer/components/SongInfoBarWrapper.jsx +77 -0
- package/src/renderer/components/StatusBar.jsx +92 -0
- package/src/renderer/components/TabNavigation.jsx +77 -0
- package/src/renderer/components/TransportControlsWrapper.jsx +69 -0
- package/src/renderer/components/creator/CreateTab.jsx +1236 -0
- package/src/renderer/dist/assets/kaiPlayer-CoMx__a_.js +2 -0
- package/src/renderer/dist/assets/kaiPlayer-CoMx__a_.js.map +1 -0
- package/src/renderer/dist/assets/microphoneEngine-BaCUhhQc.js +2 -0
- package/src/renderer/dist/assets/microphoneEngine-BaCUhhQc.js.map +1 -0
- package/src/renderer/dist/assets/player-DVrqp7N5.js +3 -0
- package/src/renderer/dist/assets/player-DVrqp7N5.js.map +1 -0
- package/src/renderer/dist/assets/songLoaders-BaTgGib4.js +2 -0
- package/src/renderer/dist/assets/songLoaders-BaTgGib4.js.map +1 -0
- package/src/renderer/dist/assets/webrtcManager-BhCHWceK.js +2 -0
- package/src/renderer/dist/assets/webrtcManager-BhCHWceK.js.map +1 -0
- package/src/renderer/dist/js/autoTuneWorklet.js +224 -0
- package/src/renderer/dist/js/micPitchDetectorWorklet.js +137 -0
- package/src/renderer/dist/js/musicAnalysisWorklet.js +216 -0
- package/src/renderer/dist/js/phaseVocoderWorklet.js +341 -0
- package/src/renderer/dist/js/soundtouch-worklet.js +1395 -0
- package/src/renderer/dist/renderer.css +1 -0
- package/src/renderer/dist/renderer.js +62 -0
- package/src/renderer/dist/renderer.js.map +1 -0
- package/src/renderer/dist/renderer.woff2 +0 -0
- package/src/renderer/hooks/useKeyboardShortcuts.js +154 -0
- package/src/renderer/index.html +24 -0
- package/src/renderer/index.html.backup +372 -0
- package/src/renderer/js/PlayerInterface.js +267 -0
- package/src/renderer/js/autoTuneWorklet.js +224 -0
- package/src/renderer/js/butterchurnVerify.js +46 -0
- package/src/renderer/js/canvas-app.js +114 -0
- package/src/renderer/js/cdgPlayer.js +685 -0
- package/src/renderer/js/kaiPlayer.js +1200 -0
- package/src/renderer/js/karaokeRenderer.js +3392 -0
- package/src/renderer/js/micPitchDetectorWorklet.js +137 -0
- package/src/renderer/js/microphoneEngine.js +656 -0
- package/src/renderer/js/musicAnalysisWorklet.js +216 -0
- package/src/renderer/js/phaseVocoderWorklet.js +341 -0
- package/src/renderer/js/player.js +232 -0
- package/src/renderer/js/referencePitchTracker.js +130 -0
- package/src/renderer/js/songLoaders.js +334 -0
- package/src/renderer/js/soundtouch-worklet.js +1395 -0
- package/src/renderer/js/webrtcManager.js +511 -0
- package/src/renderer/lib/butterchurn.min.js +6739 -0
- package/src/renderer/lib/butterchurnPresets.min.js +1 -0
- package/src/renderer/lib/cdgraphics-wrapper.js +16 -0
- package/src/renderer/lib/cdgraphics.js +299 -0
- package/src/renderer/public/js/autoTuneWorklet.js +224 -0
- package/src/renderer/public/js/micPitchDetectorWorklet.js +137 -0
- package/src/renderer/public/js/musicAnalysisWorklet.js +216 -0
- package/src/renderer/public/js/phaseVocoderWorklet.js +341 -0
- package/src/renderer/public/js/soundtouch-worklet.js +1395 -0
- package/src/renderer/react-entry.jsx +44 -0
- package/src/renderer/styles/tailwind.css +106 -0
- package/src/renderer/utils/qrCodeGenerator.js +98 -0
- package/src/renderer/vite.config.js +31 -0
- package/src/shared/adapters/BridgeInterface.js +195 -0
- package/src/shared/components/EffectsPanel.jsx +177 -0
- package/src/shared/components/LibraryPanel.jsx +701 -0
- package/src/shared/components/LineDetailCanvas.jsx +167 -0
- package/src/shared/components/LyricLine.jsx +505 -0
- package/src/shared/components/LyricRejection.jsx +84 -0
- package/src/shared/components/LyricSuggestion.jsx +80 -0
- package/src/shared/components/LyricsEditorCanvas.jsx +271 -0
- package/src/shared/components/MixerPanel.jsx +94 -0
- package/src/shared/components/PlayerControls.jsx +206 -0
- package/src/shared/components/PortalSelect.jsx +239 -0
- package/src/shared/components/QueueList.jsx +365 -0
- package/src/shared/components/QuickSearch.jsx +126 -0
- package/src/shared/components/RequestsList.jsx +121 -0
- package/src/shared/components/SongEditor.jsx +1362 -0
- package/src/shared/components/SongInfoBar.jsx +81 -0
- package/src/shared/components/ThemeToggle.jsx +106 -0
- package/src/shared/components/Toast.jsx +30 -0
- package/src/shared/components/VisualizationSettings.jsx +243 -0
- package/src/shared/constants.js +95 -0
- package/src/shared/context/BridgeContext.jsx +32 -0
- package/src/shared/contexts/AudioContext.jsx +37 -0
- package/src/shared/contexts/PlayerContext.jsx +66 -0
- package/src/shared/contexts/SettingsContext.jsx +50 -0
- package/src/shared/defaults.js +158 -0
- package/src/shared/formatUtils.js +59 -0
- package/src/shared/formatUtils.test.js +207 -0
- package/src/shared/hooks/useAppState.js +97 -0
- package/src/shared/hooks/useAudioEngine.js +264 -0
- package/src/shared/hooks/usePlayer.js +89 -0
- package/src/shared/hooks/useSettingsPersistence.js +74 -0
- package/src/shared/hooks/useWebRTC.js +118 -0
- package/src/shared/ipcContracts.js +299 -0
- package/src/shared/package.json +3 -0
- package/src/shared/services/creatorService.js +373 -0
- package/src/shared/services/creatorService.test.js +413 -0
- package/src/shared/services/editorService.js +213 -0
- package/src/shared/services/editorService.test.js +219 -0
- package/src/shared/services/effectsService.js +271 -0
- package/src/shared/services/effectsService.test.js +418 -0
- package/src/shared/services/libraryService.js +438 -0
- package/src/shared/services/libraryService.test.js +474 -0
- package/src/shared/services/mixerService.js +172 -0
- package/src/shared/services/mixerService.test.js +399 -0
- package/src/shared/services/playerService.js +221 -0
- package/src/shared/services/playerService.test.js +357 -0
- package/src/shared/services/preferencesService.js +219 -0
- package/src/shared/services/queueService.js +226 -0
- package/src/shared/services/queueService.test.js +430 -0
- package/src/shared/services/requestsService.js +155 -0
- package/src/shared/services/requestsService.test.js +362 -0
- package/src/shared/services/serverSettingsService.js +151 -0
- package/src/shared/services/settingsService.js +257 -0
- package/src/shared/services/settingsService.test.js +295 -0
- package/src/shared/state/StateManager.js +263 -0
- package/src/shared/utils/audio.js +42 -0
- package/src/shared/utils/format.js +32 -0
- package/src/shared/utils/lyricsUtils.js +162 -0
- package/src/test/setup.js +40 -0
- package/src/utils/cdgLoader.js +180 -0
- package/src/utils/m4aLoader.js +333 -0
- package/src/web/App.jsx +578 -0
- package/src/web/adapters/WebBridge.js +428 -0
- package/src/web/components/PlayerSettingsPanel.jsx +231 -0
- package/src/web/components/SongSearch.jsx +180 -0
- package/src/web/dist/assets/index-0H-RnRrV.js +51 -0
- package/src/web/dist/assets/index-0H-RnRrV.js.map +1 -0
- package/src/web/dist/assets/index-DYW2zB0u.css +1 -0
- package/src/web/dist/index.html +15 -0
- package/src/web/index.html +14 -0
- package/src/web/main.jsx +10 -0
- package/src/web/package-lock.json +1765 -0
- package/src/web/pages/SongRequestPage.jsx +619 -0
- package/src/web/styles/tailwind.css +68 -0
- package/src/web/vite.config.js +27 -0
- package/static/fonts/material-icons.woff2 +0 -0
- package/static/images/butterchurn-screenshots/Aderrasi - Potion of Spirits.png +0 -0
- package/static/images/butterchurn-screenshots/Aderrasi - Songflower _Moss Posy_.png +0 -0
- package/static/images/butterchurn-screenshots/Aderrasi - Storm of the Eye _Thunder_ - mash0000 - quasi pseudo meta concentrics.png +0 -0
- package/static/images/butterchurn-screenshots/Aderrasi _ Geiss - Airhandler _Kali Mix_ - Canvas Mix.png +0 -0
- 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
- package/static/images/butterchurn-screenshots/Cope - The Neverending Explosion of Red Liquid Fire.png +0 -0
- proton lights __Krash_s beat code_ _Phat_remix02b.png +0 -0
- package/static/images/butterchurn-screenshots/Eo_S_ _ Phat - cubetrace - v2.png +0 -0
- package/static/images/butterchurn-screenshots/Eo_S_ _ Zylot - skylight _Stained Glass Majesty mix_.png +0 -0
- package/static/images/butterchurn-screenshots/Flexi - alien fish pond.png +0 -0
- package/static/images/butterchurn-screenshots/Flexi - area 51.png +0 -0
- package/static/images/butterchurn-screenshots/Flexi - infused with the spiral.png +0 -0
- package/static/images/butterchurn-screenshots/Flexi - mindblob _shiny mix_.png +0 -0
- package/static/images/butterchurn-screenshots/Flexi - mindblob mix.png +0 -0
- package/static/images/butterchurn-screenshots/Flexi - predator-prey-spirals.png +0 -0
- package/static/images/butterchurn-screenshots/Flexi - smashing fractals _acid etching mix_.png +0 -0
- package/static/images/butterchurn-screenshots/Flexi - truly soft piece of software - this is generic texturing _Jelly_ .png +0 -0
- package/static/images/butterchurn-screenshots/Flexi _ Martin - astral projection.png +0 -0
- package/static/images/butterchurn-screenshots/Flexi _ Martin - cascading decay swing.png +0 -0
- package/static/images/butterchurn-screenshots/Flexi _ amandio c - piercing 05 - Kopie _2_ - Kopie.png +0 -0
- package/static/images/butterchurn-screenshots/Flexi _ stahlregen - jelly showoff parade.png +0 -0
- package/static/images/butterchurn-screenshots/Flexi_ fishbrain_ Geiss _ Martin - tokamak witchery.png +0 -0
- package/static/images/butterchurn-screenshots/Flexi_ martin _ geiss - dedicated to the sherwin maxawow.png +0 -0
- package/static/images/butterchurn-screenshots/Fumbling_Foo _ Flexi_ Martin_ Orb_ Unchained - Star Nova v7b.png +0 -0
- package/static/images/butterchurn-screenshots/Geiss - Cauldron - painterly 2 _saturation remix_.png +0 -0
- package/static/images/butterchurn-screenshots/Geiss - Reaction Diffusion 2.png +0 -0
- package/static/images/butterchurn-screenshots/Geiss - Spiral Artifact.png +0 -0
- package/static/images/butterchurn-screenshots/Geiss - Thumb Drum.png +0 -0
- package/static/images/butterchurn-screenshots/Geiss _ Flexi _ Martin - disconnected.png +0 -0
- package/static/images/butterchurn-screenshots/Geiss_ Flexi _ Stahlregen - Thumbdrum Tokamak _crossfiring aftermath jelly mashup_.png +0 -0
- package/static/images/butterchurn-screenshots/Goody - The Wild Vort.png +0 -0
- package/static/images/butterchurn-screenshots/Idiot - Star Of Annon.png +0 -0
- package/static/images/butterchurn-screenshots/Krash _ Illusion - Spiral Movement.png +0 -0
- package/static/images/butterchurn-screenshots/Martin - QBikal - Surface Turbulence IIb.png +0 -0
- package/static/images/butterchurn-screenshots/Martin - acid wiring.png +0 -0
- package/static/images/butterchurn-screenshots/Martin - charisma.png +0 -0
- package/static/images/butterchurn-screenshots/Martin - liquid arrows.png +0 -0
- package/static/images/butterchurn-screenshots/Milk Artist At our Best - FED - SlowFast Ft AdamFX n Martin - HD CosmoFX.png +0 -0
- package/static/images/butterchurn-screenshots/ORB - Waaa.png +0 -0
- package/static/images/butterchurn-screenshots/Phat_fiShbRaiN_Eo_S_Mandala_Chasers_remix.png +0 -0
- package/static/images/butterchurn-screenshots/Rovastar - Oozing Resistance.png +0 -0
- package/static/images/butterchurn-screenshots/Rovastar _ Loadus _ Geiss - FractalDrop _Triple Mix_.png +0 -0
- package/static/images/butterchurn-screenshots/TonyMilkdrop - Leonardo Da Vinci_s Balloon _Flexi - merry-go-round _ techstyle_.png +0 -0
- package/static/images/butterchurn-screenshots/TonyMilkdrop - Magellan_s Nebula _Flexi - you enter first _ multiverse_.png +0 -0
- package/static/images/butterchurn-screenshots/Unchained - Rewop.png +0 -0
- package/static/images/butterchurn-screenshots/Unchained - Unified Drag 2.png +0 -0
- package/static/images/butterchurn-screenshots/Unchained _ Rovastar - Wormhole Pillars _Hall of Shadows mix_.png +0 -0
- package/static/images/butterchurn-screenshots/Zylot - Paint Spill _Music Reactive Paint Mix_.png +0 -0
- package/static/images/butterchurn-screenshots/Zylot - Star Ornament.png +0 -0
- package/static/images/butterchurn-screenshots/Zylot - True Visionary _Final Mix_.png +0 -0
- package/static/images/butterchurn-screenshots/_Aderrasi - Wanderer in Curved Space - mash0000 - faclempt kibitzing meshuggana schmaltz _Geiss color mix_.png +0 -0
- package/static/images/butterchurn-screenshots/_Geiss - Artifact 01.png +0 -0
- package/static/images/butterchurn-screenshots/_Geiss - Desert Rose 2.png +0 -0
- package/static/images/butterchurn-screenshots/_Geiss - untitled.png +0 -0
- package/static/images/butterchurn-screenshots/_Mig_049.png +0 -0
- package/static/images/butterchurn-screenshots/_Mig_085.png +0 -0
- package/static/images/butterchurn-screenshots/_Rovastar _ Geiss - Hurricane Nightmare _Posterize Mix_.png +0 -0
- package/static/images/butterchurn-screenshots/___ Royal - Mashup _197_.png +0 -0
- package/static/images/butterchurn-screenshots/___ Royal - Mashup _220_.png +0 -0
- package/static/images/butterchurn-screenshots/___ Royal - Mashup _431_.png +0 -0
- package/static/images/butterchurn-screenshots/cope _ martin - mother-of-pearl.png +0 -0
- package/static/images/butterchurn-screenshots/fiShbRaiN _ Flexi - witchcraft 2_0.png +0 -0
- package/static/images/butterchurn-screenshots/flexi - bouncing balls _double mindblob neon mix_.png +0 -0
- package/static/images/butterchurn-screenshots/flexi - mom_ why the sky looks different today.png +0 -0
- package/static/images/butterchurn-screenshots/flexi - patternton_ district of media_ capitol of the united abstractions of fractopia.png +0 -0
- package/static/images/butterchurn-screenshots/flexi - swing out on the spiral.png +0 -0
- package/static/images/butterchurn-screenshots/flexi - what is the matrix.png +0 -0
- package/static/images/butterchurn-screenshots/flexi _ amandio c - organic _random mashup_.png +0 -0
- package/static/images/butterchurn-screenshots/flexi _ amandio c - organic12-3d-2_milk.png +0 -0
- package/static/images/butterchurn-screenshots/flexi _ fishbrain - neon mindblob grafitti.png +0 -0
- package/static/images/butterchurn-screenshots/flexi _ geiss - pogo cubes vs_ tokamak vs_ game of life _stahls jelly 4_5 finish_.png +0 -0
- package/static/images/butterchurn-screenshots/high-altitude basket unraveling - singh grooves nitrogen argon nz_.png +0 -0
- package/static/images/butterchurn-screenshots/martin - The Bridge of Khazad-Dum.png +0 -0
- package/static/images/butterchurn-screenshots/martin - angel flight.png +0 -0
- package/static/images/butterchurn-screenshots/martin - another kind of groove.png +0 -0
- package/static/images/butterchurn-screenshots/martin - bombyx mori.png +0 -0
- package/static/images/butterchurn-screenshots/martin - castle in the air.png +0 -0
- package/static/images/butterchurn-screenshots/martin - chain breaker.png +0 -0
- package/static/images/butterchurn-screenshots/martin - disco mix 4.png +0 -0
- package/static/images/butterchurn-screenshots/martin - extreme heat.png +0 -0
- package/static/images/butterchurn-screenshots/martin - frosty caves 2.png +0 -0
- package/static/images/butterchurn-screenshots/martin - fruit machine.png +0 -0
- package/static/images/butterchurn-screenshots/martin - ghost city.png +0 -0
- package/static/images/butterchurn-screenshots/martin - glass corridor.png +0 -0
- package/static/images/butterchurn-screenshots/martin - infinity _2010 update_.png +0 -0
- package/static/images/butterchurn-screenshots/martin - mandelbox explorer - high speed demo version.png +0 -0
- package/static/images/butterchurn-screenshots/martin - mucus cervix.png +0 -0
- package/static/images/butterchurn-screenshots/martin - reflections on black tiles.png +0 -0
- package/static/images/butterchurn-screenshots/martin - stormy sea _2010 update_.png +0 -0
- package/static/images/butterchurn-screenshots/martin - witchcraft reloaded.png +0 -0
- package/static/images/butterchurn-screenshots/martin _ flexi - diamond cutter _prismaticvortex_com_ - camille - i wish i wish i wish i was constrained.png +0 -0
- package/static/images/butterchurn-screenshots/martin _shadow harlequins shape code_ - fata morgana.png +0 -0
- package/static/images/butterchurn-screenshots/martin_ flexi_ fishbrain _ sto - enterstate _random mashup_.png +0 -0
- package/static/images/butterchurn-screenshots/sawtooth grin roam.png +0 -0
- package/static/images/butterchurn-screenshots/shifter - dark tides bdrv mix 2.png +0 -0
- 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
- package/static/images/butterchurn-screenshots/suksma - heretical crosscut playpen.png +0 -0
- package/static/images/butterchurn-screenshots/suksma - uninitialized variabowl _hydroponic chronic_.png +0 -0
- package/static/images/butterchurn-screenshots/suksma - vector exp 1 - couldn_t not.png +0 -0
- package/static/images/butterchurn-screenshots/yin - 191 - Temporal singularities.png +0 -0
- package/static/images/logo-512.png +0 -0
- package/static/images/logo.png +0 -0
- package/static/loukai-logo.png +0 -0
- 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
|
+
};
|