karaoke-gen 0.71.42__py3-none-any.whl → 0.75.53__py3-none-any.whl

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 (38) hide show
  1. karaoke_gen/__init__.py +32 -1
  2. karaoke_gen/audio_fetcher.py +1220 -67
  3. karaoke_gen/audio_processor.py +15 -3
  4. karaoke_gen/instrumental_review/server.py +154 -860
  5. karaoke_gen/instrumental_review/static/index.html +1529 -0
  6. karaoke_gen/karaoke_finalise/karaoke_finalise.py +87 -2
  7. karaoke_gen/karaoke_gen.py +131 -14
  8. karaoke_gen/lyrics_processor.py +172 -4
  9. karaoke_gen/utils/bulk_cli.py +3 -0
  10. karaoke_gen/utils/cli_args.py +7 -4
  11. karaoke_gen/utils/gen_cli.py +221 -5
  12. karaoke_gen/utils/remote_cli.py +786 -43
  13. {karaoke_gen-0.71.42.dist-info → karaoke_gen-0.75.53.dist-info}/METADATA +109 -4
  14. {karaoke_gen-0.71.42.dist-info → karaoke_gen-0.75.53.dist-info}/RECORD +37 -31
  15. lyrics_transcriber/core/controller.py +76 -2
  16. lyrics_transcriber/frontend/package.json +1 -1
  17. lyrics_transcriber/frontend/src/App.tsx +6 -4
  18. lyrics_transcriber/frontend/src/api.ts +25 -10
  19. lyrics_transcriber/frontend/src/components/Header.tsx +38 -12
  20. lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +17 -3
  21. lyrics_transcriber/frontend/src/components/LyricsSynchronizer/SyncControls.tsx +185 -0
  22. lyrics_transcriber/frontend/src/components/LyricsSynchronizer/TimelineCanvas.tsx +704 -0
  23. lyrics_transcriber/frontend/src/components/LyricsSynchronizer/UpcomingWordsBar.tsx +80 -0
  24. lyrics_transcriber/frontend/src/components/LyricsSynchronizer/index.tsx +905 -0
  25. lyrics_transcriber/frontend/src/components/ModeSelectionModal.tsx +127 -0
  26. lyrics_transcriber/frontend/src/components/ReplaceAllLyricsModal.tsx +190 -542
  27. lyrics_transcriber/frontend/tsconfig.tsbuildinfo +1 -1
  28. lyrics_transcriber/frontend/web_assets/assets/{index-DdJTDWH3.js → index-BECn1o8Q.js} +1802 -553
  29. lyrics_transcriber/frontend/web_assets/assets/index-BECn1o8Q.js.map +1 -0
  30. lyrics_transcriber/frontend/web_assets/index.html +1 -1
  31. lyrics_transcriber/output/countdown_processor.py +39 -0
  32. lyrics_transcriber/review/server.py +5 -5
  33. lyrics_transcriber/transcribers/audioshake.py +96 -7
  34. lyrics_transcriber/types.py +14 -12
  35. lyrics_transcriber/frontend/web_assets/assets/index-DdJTDWH3.js.map +0 -1
  36. {karaoke_gen-0.71.42.dist-info → karaoke_gen-0.75.53.dist-info}/WHEEL +0 -0
  37. {karaoke_gen-0.71.42.dist-info → karaoke_gen-0.75.53.dist-info}/entry_points.txt +0 -0
  38. {karaoke_gen-0.71.42.dist-info → karaoke_gen-0.75.53.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: karaoke-gen
3
- Version: 0.71.42
3
+ Version: 0.75.53
4
4
  Summary: Generate karaoke videos with synchronized lyrics. Handles the entire process from downloading audio and lyrics to creating the final video with title screens.
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -22,7 +22,7 @@ Requires-Dist: dropbox (>=12)
22
22
  Requires-Dist: fastapi (>=0.104.0)
23
23
  Requires-Dist: fetch-lyrics-from-genius (>=0.1)
24
24
  Requires-Dist: ffmpeg-python (>=0.2.0,<0.3.0)
25
- Requires-Dist: flacfetch (>=0.3)
25
+ Requires-Dist: flacfetch (>=0.9.0)
26
26
  Requires-Dist: fonttools (>=4.55)
27
27
  Requires-Dist: google-api-python-client
28
28
  Requires-Dist: google-auth
@@ -48,6 +48,8 @@ Requires-Dist: lyrics-converter (>=0.2.1)
48
48
  Requires-Dist: lyricsgenius (>=3)
49
49
  Requires-Dist: matplotlib (>=3)
50
50
  Requires-Dist: metaphone (>=0.6)
51
+ Requires-Dist: mutagen (>=1.47)
52
+ Requires-Dist: nest-asyncio (>=1.5)
51
53
  Requires-Dist: nltk (>=3.9)
52
54
  Requires-Dist: numpy (>=2)
53
55
  Requires-Dist: ollama (>=0.4.7)
@@ -93,7 +95,7 @@ Description-Content-Type: text/markdown
93
95
  # Karaoke Generator 🎶 🎥 🚀
94
96
 
95
97
  ![PyPI - Version](https://img.shields.io/pypi/v/karaoke-gen)
96
- ![Python Version](https://img.shields.io/badge/python-3.10+-blue)
98
+ ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/karaoke-gen)
97
99
  ![Tests](https://github.com/nomadkaraoke/karaoke-gen/workflows/Test%20and%20Publish/badge.svg)
98
100
  ![Test Coverage](https://codecov.io/gh/nomadkaraoke/karaoke-gen/branch/main/graph/badge.svg)
99
101
 
@@ -146,10 +148,44 @@ pip install karaoke-gen
146
148
  This installs both `karaoke-gen` (local) and `karaoke-gen-remote` (cloud) CLIs.
147
149
 
148
150
  ### Requirements
149
- - Python 3.10+
151
+ - Python 3.10-3.13
150
152
  - FFmpeg
151
153
  - For local processing: CUDA-capable GPU or Apple Silicon CPU recommended
152
154
 
155
+ ### Transcription Provider Setup
156
+
157
+ **Transcription is required** for creating karaoke videos with synchronized lyrics. The system needs word-level timing data to display lyrics in sync with the music.
158
+
159
+ #### Option 1: AudioShake (Recommended)
160
+ Commercial service with high-quality transcription. Best for production use.
161
+
162
+ ```bash
163
+ export AUDIOSHAKE_API_TOKEN="your_audioshake_token"
164
+ ```
165
+
166
+ Get an API key at [https://www.audioshake.ai/](https://www.audioshake.ai/) - business only, at time of writing this.
167
+
168
+ #### Option 2: Whisper via RunPod
169
+ Open-source alternative using OpenAI's Whisper model on RunPod infrastructure.
170
+
171
+ ```bash
172
+ export RUNPOD_API_KEY="your_runpod_key"
173
+ export WHISPER_RUNPOD_ID="your_whisper_endpoint_id"
174
+ ```
175
+
176
+ Set up a Whisper endpoint at [https://www.runpod.io/](https://www.runpod.io/)
177
+
178
+ #### Without Transcription (Instrumental Only)
179
+ If you don't need synchronized lyrics, use the `--skip-lyrics` flag:
180
+
181
+ ```bash
182
+ karaoke-gen --skip-lyrics "Artist" "Title"
183
+ ```
184
+
185
+ This creates an instrumental-only karaoke video without lyrics overlay.
186
+
187
+ > **Note:** See `lyrics_transcriber_temp/README.md` for detailed transcription provider configuration options.
188
+
153
189
  ---
154
190
 
155
191
  ## 🖥️ Local CLI (`karaoke-gen`)
@@ -275,6 +311,8 @@ karaoke-gen-remote \
275
311
  | `REVIEW_UI_URL` | Lyrics review UI URL | `https://lyrics.nomadkaraoke.com` |
276
312
  | `POLL_INTERVAL` | Seconds between status polls | `5` |
277
313
 
314
+ **Note:** The `REVIEW_UI_URL` defaults to the hosted lyrics review UI. For local development, set it to `http://localhost:5173` if you're running the frontend dev server.
315
+
278
316
  ### Authentication
279
317
 
280
318
  The backend uses token-based authentication for admin operations (bulk delete, internal worker triggers). For basic job submission and monitoring, authentication is optional.
@@ -565,6 +603,73 @@ Check backend health status.
565
603
 
566
604
  ---
567
605
 
606
+ ## 🔧 Troubleshooting
607
+
608
+ ### "No suitable files found for processing"
609
+
610
+ This error occurs during the finalisation step when the `(With Vocals).mkv` file is missing. This file is created during lyrics transcription.
611
+
612
+ **Most common cause:** No transcription provider configured.
613
+
614
+ **Quick fix:**
615
+ 1. Check if transcription providers are configured:
616
+ ```bash
617
+ echo $AUDIOSHAKE_API_TOKEN
618
+ echo $RUNPOD_API_KEY
619
+ ```
620
+
621
+ 2. If both are empty, set up a provider (see [Transcription Provider Setup](#transcription-provider-setup))
622
+
623
+ 3. Or use `--skip-lyrics` for instrumental-only karaoke:
624
+ ```bash
625
+ karaoke-gen --skip-lyrics "Artist" "Title"
626
+ ```
627
+
628
+ **Other causes:**
629
+ - Invalid API credentials - verify your tokens are correct and active
630
+ - API service unavailable - check service status pages
631
+ - Network connectivity issues - ensure you can reach the API endpoints
632
+ - Transcription timeout - try again or use a different provider
633
+
634
+ ### Transcription Fails Silently
635
+
636
+ If karaoke-gen runs without errors but produces no synchronized lyrics:
637
+
638
+ 1. **Check logs** - Run with `--log_level debug` for detailed output:
639
+ ```bash
640
+ karaoke-gen --log_level debug "Artist" "Title"
641
+ ```
642
+
643
+ 2. **Verify environment variables** - Ensure API tokens are exported in your shell:
644
+ ```bash
645
+ # Check if set
646
+ printenv | grep -E "(AUDIOSHAKE|RUNPOD|WHISPER)"
647
+
648
+ # Set in current session
649
+ export AUDIOSHAKE_API_TOKEN="your_token"
650
+ ```
651
+
652
+ 3. **Test API connectivity** - Verify you can reach the transcription service
653
+
654
+ ### "No lyrics found from any source"
655
+
656
+ This warning means no reference lyrics were fetched from online sources (Genius, Spotify, Musixmatch). The transcription will still work, but auto-correction may be less accurate.
657
+
658
+ **To fix:**
659
+ - Set `GENIUS_API_TOKEN` for Genius lyrics
660
+ - Set `SPOTIFY_COOKIE_SP_DC` for Spotify lyrics
661
+ - Set `RAPIDAPI_KEY` for Musixmatch lyrics
662
+ - Or provide lyrics manually with `--lyrics_file /path/to/lyrics.txt`
663
+
664
+ ### Video Quality Issues
665
+
666
+ If the output video has quality problems:
667
+ - Ensure FFmpeg is properly installed: `ffmpeg -version`
668
+ - Check available codecs: `ffmpeg -codecs`
669
+ - For 4K output, ensure sufficient disk space (10GB+ per track)
670
+
671
+ ---
672
+
568
673
  ## 🧪 Development
569
674
 
570
675
  ### Running Tests
@@ -1,18 +1,19 @@
1
- karaoke_gen/__init__.py,sha256=ViryQjs8ALc8A7mqJGHu028zajF5-Za_etFagXlo6kk,269
2
- karaoke_gen/audio_fetcher.py,sha256=No3LM7QIiT2B3KuzQFayCwqGfBuM5fSI22y2a_Y07EA,15013
3
- karaoke_gen/audio_processor.py,sha256=vB8jPeTHjE9RpRNMwQjaCpgu1HlD6HNMDLf4riDYsGE,39309
1
+ karaoke_gen/__init__.py,sha256=wHpDbURJxmJAMNZ0uQjISv5MIT7KD9RWYi15xlYgEhU,1351
2
+ karaoke_gen/audio_fetcher.py,sha256=A1c7HxUbV4inhGuj9SySr3zkOSGKfAFovXu2TKzodSA,63811
3
+ karaoke_gen/audio_processor.py,sha256=YCMsxh-OSzC6h1Oqht8gh48j4KaRDuRIMUUsNNQxJeY,39913
4
4
  karaoke_gen/config.py,sha256=LBZKpvwSgta8YoVX2GFFW-4CP22AyRtqsBn-KCeh8eg,2499
5
5
  karaoke_gen/file_handler.py,sha256=jnPc4kFtG-PX-IVPHYWa7maXd4lNmbkKd1HogDNzgN8,16674
6
6
  karaoke_gen/instrumental_review/__init__.py,sha256=91K9wPWfQnOqbINuhxtErPnXyY0gijdiF-69n-p3334,1382
7
7
  karaoke_gen/instrumental_review/analyzer.py,sha256=Heg8TbrwM4g5IV7bavmO6EfVD4M0UGMs_qUoZguU_OQ,15022
8
8
  karaoke_gen/instrumental_review/editor.py,sha256=_DGTjKMk5WhoGtLGtTvHzU522LJyQQ_DSY1r8fULuiA,11568
9
9
  karaoke_gen/instrumental_review/models.py,sha256=cUSb_JheJK0cGdKx9f59-9sRvRrhrgdTdKBzQN3lHto,5226
10
- karaoke_gen/instrumental_review/server.py,sha256=BQOr421P9EL0FfGWtgVDgFqWPSPldgvScwXxP59nHXE,47725
10
+ karaoke_gen/instrumental_review/server.py,sha256=Ick90X77t2EeMRwtx2U08sSybadQyWH7G0tDG-4JqP4,19377
11
+ karaoke_gen/instrumental_review/static/index.html,sha256=EjMFxCQJOUSrsgwIXAW3R4bN6hYxDLmL4MHzoXxI4f0,59362
11
12
  karaoke_gen/instrumental_review/waveform.py,sha256=Q6LBPZrJAD6mzZ7TmRf3Tf4gwYhUYTHumJKytLs3hSg,12940
12
13
  karaoke_gen/karaoke_finalise/__init__.py,sha256=HqZ7TIhgt_tYZ-nb_NNCaejWAcF_aK-7wJY5TaW_keM,46
13
- karaoke_gen/karaoke_finalise/karaoke_finalise.py,sha256=sOyQCDM42YypZJpAKrHvxEiu3IEyQPBMRSjvm486idg,88828
14
- karaoke_gen/karaoke_gen.py,sha256=uVATmUyDNrF1UeQqtIpNwHhffIQeiQjgCjNVcTSz17w,50353
15
- karaoke_gen/lyrics_processor.py,sha256=R8vO0tF7-k5PVDiXrUMGd-4Fqa4M3QNInLy9Y3XhsgA,14912
14
+ karaoke_gen/karaoke_finalise/karaoke_finalise.py,sha256=Wn1KcdRyINT63UxKUPT9uB-bsrFVih0Im_cjXtequS0,93534
15
+ karaoke_gen/karaoke_gen.py,sha256=tfoywlSEI9qOi1krHTBafumXl3bcWxn6w0AN2vQ1nHs,57048
16
+ karaoke_gen/lyrics_processor.py,sha256=JrbTLmMR_jvcpuxsPihLXlCBKkFNKqYHvraw3_3KCPI,23263
16
17
  karaoke_gen/metadata.py,sha256=SZW6TuUpkGGU98gRdjPfrR8F4vWXjnfCSGry2XD5_A4,6689
17
18
  karaoke_gen/pipeline/__init__.py,sha256=-MZnba4qobr1qGDamG9CieLl2pWCZMEB5_Yur62RKeM,2106
18
19
  karaoke_gen/pipeline/base.py,sha256=yg4LIm7Mc9ER0zCmZcUv4huEkotSSXK_0OAFio-TSNI,6235
@@ -33,10 +34,10 @@ karaoke_gen/resources/Oswald-SemiBold.ttf,sha256=G-vSJeeyEVft7D4s7FZQtGfXAViWPjz
33
34
  karaoke_gen/resources/Zurich_Cn_BT_Bold.ttf,sha256=WNG5LOQ-uGUF_WWT5aQHzVbyWvQqGO5sZ4E-nRmvPuI,37780
34
35
  karaoke_gen/style_loader.py,sha256=13010BVxwVgamRn8K8Z9fNgLBdPs9LFmWMRMLyBdits,17908
35
36
  karaoke_gen/utils/__init__.py,sha256=FpOHyeBRB06f3zMoLBUJHTDZACrabg-DoyBTxNKYyNY,722
36
- karaoke_gen/utils/bulk_cli.py,sha256=bBRHfhvi-wkoNjAoq5rzVkaOwOraoiUhXNQY5rBsX18,19167
37
- karaoke_gen/utils/cli_args.py,sha256=PmOV1CbxE517O0NprXPPgZfLmREinrtOKk5dD9LLHH0,17862
38
- karaoke_gen/utils/gen_cli.py,sha256=DbCjoLfKAD22uh60gYa1jtZq62r6RvZK0SP_L10_pVY,33490
39
- karaoke_gen/utils/remote_cli.py,sha256=nS6wpNkgAZDNi9lwYXYEzdLeZtQvsPk-Nd8ckWkULus,109448
37
+ karaoke_gen/utils/bulk_cli.py,sha256=s2SXTnnQf7TM8Fk8yz9Cfd3Xl08uHBnve_rTkf4wE5g,19337
38
+ karaoke_gen/utils/cli_args.py,sha256=Td8fE1FisY1fcQuOoKlAQ6B3NF4bxGvSPDaQlpfY3T0,18172
39
+ karaoke_gen/utils/gen_cli.py,sha256=ahoVp_n298O8yUmFOBDsm4mLXgTuEb80mBnz4_mo4SM,44137
40
+ karaoke_gen/utils/remote_cli.py,sha256=l1iHUz_hrsbAfTvGDhA4gzr2g1CFBFc9np0nQ1jk1tQ,144629
40
41
  karaoke_gen/video_background_processor.py,sha256=p3sryMxmkori4Uy2MYgmlk5_QQ7Uh9IoVJLAdkdLIUI,15124
41
42
  karaoke_gen/video_generator.py,sha256=B7BQBrjkyvk3L3sctnPXnvr1rzkw0NYx5UCAl0ZiVx0,18464
42
43
  lyrics_transcriber/__init__.py,sha256=g9ZbJg9U1qo7XzrC25J3bTKcNzzwUJWDVdi_7-hjcM4,412
@@ -44,7 +45,7 @@ lyrics_transcriber/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3
44
45
  lyrics_transcriber/cli/cli_main.py,sha256=F72ENLTj934bXjHAUbRm0toCK73qnuJhwEm9agBVKHQ,11596
45
46
  lyrics_transcriber/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
47
  lyrics_transcriber/core/config.py,sha256=AM6RZKll8tzdZtMLgvHRQb1SxiXVPek0q4vmSWVUvEo,1368
47
- lyrics_transcriber/core/controller.py,sha256=jPoJ-r1jZ94Xp8ARN9lpSjsMAqPXuGHhwnQy_FmkBvI,25216
48
+ lyrics_transcriber/core/controller.py,sha256=laeUakqT-0CoIyoBWYvv7kWxX-_wefWRwg2xrz84gRg,29432
48
49
  lyrics_transcriber/correction/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
49
50
  lyrics_transcriber/correction/agentic/__init__.py,sha256=p7PHiebuvRs8RDlPDs-9gLZKzXG5KfWg3fFCdDhY6pE,222
50
51
  lyrics_transcriber/correction/agentic/adapter.py,sha256=Z0JBTAA7xlSdctCHqO9nBMl78C4XmqsLKKtS6BvNZNI,2912
@@ -124,7 +125,7 @@ lyrics_transcriber/frontend/REPLACE_ALL_FUNCTIONALITY.md,sha256=iRZbicW5satHel9g
124
125
  lyrics_transcriber/frontend/__init__.py,sha256=nW8acRSWTjXoRwGqcTU4w-__X7tMAE0iXL0uihBN3CU,836
125
126
  lyrics_transcriber/frontend/eslint.config.js,sha256=3ADH23ANA4NNBKFy6nCVk65e8bx1DrVd_FIaYNnhuqA,734
126
127
  lyrics_transcriber/frontend/index.html,sha256=u1m7042a1kLWZVIElYQ9y-lzfIAdYJGtQE-i4Zjc3xY,806
127
- lyrics_transcriber/frontend/package.json,sha256=6Qsc0FSVRyLhnklgaseLu9kqOEeMwjBuxdi7A_PD9zM,1182
128
+ lyrics_transcriber/frontend/package.json,sha256=5krvXBc9deVYXREN9Pxw5N6ohiAsSCnPXnXlcb6p-U0,1182
128
129
  lyrics_transcriber/frontend/public/android-chrome-192x192.png,sha256=lg-6aPF5mGLiuG7LyftZk_0RI41srmpA8wj-NkaaQms,17632
129
130
  lyrics_transcriber/frontend/public/android-chrome-512x512.png,sha256=x-zuKT3NYsTqAWzhKRTZeD4-0uYoUjqMPZpKTChqNJ8,123447
130
131
  lyrics_transcriber/frontend/public/apple-touch-icon.png,sha256=6y5vGra54w5oc8VP6sn2JjoQtN9hWTKn0YPhmdlmfU0,16188
@@ -132,8 +133,8 @@ lyrics_transcriber/frontend/public/favicon-16x16.png,sha256=2wy_7ZmVS4d7umByJVHQ
132
133
  lyrics_transcriber/frontend/public/favicon-32x32.png,sha256=6TyrRMIw6G8dpG4_QWVTY8DMxo0bIGzc_9aOJrkiJKM,1297
133
134
  lyrics_transcriber/frontend/public/favicon.ico,sha256=ZK7QvdBuZp0QxPkluCW4IKxfleFmFLp1KkG_d5xmx7E,15406
134
135
  lyrics_transcriber/frontend/public/nomad-karaoke-logo.png,sha256=jTTBFXV6hGJGolZYQ-dIjgQQbMsehk5XGtsllhLrdzg,212641
135
- lyrics_transcriber/frontend/src/App.tsx,sha256=f1-dp-MU8vap18eAXacwVDO5P4eE2iG9zSvjau-7NJs,6533
136
- lyrics_transcriber/frontend/src/api.ts,sha256=qYpH_KRkco1pYU4oJ8XgV4Y6xrTOPGngxwxB0NwlKro,8163
136
+ lyrics_transcriber/frontend/src/App.tsx,sha256=XI93-pX2p76ID45XUQTitDclUZHZ1kxhZZB2VriMV8k,6747
137
+ lyrics_transcriber/frontend/src/api.ts,sha256=GcjbOrlU7EdUpZ7MUPFqE1rtH-ckdw8wHtgyQxWateY,8648
137
138
  lyrics_transcriber/frontend/src/components/AIFeedbackModal.tsx,sha256=N-M8LNKE3nCjrSkWTf8ObWSdBqD0Cv3Xiz_wVv9OtFo,3133
138
139
  lyrics_transcriber/frontend/src/components/AddLyricsModal.tsx,sha256=ubJwQewryjUrXwpBkITQNu4POhoUtDbNA93cqa-yJKY,3416
139
140
  lyrics_transcriber/frontend/src/components/AgenticCorrectionMetrics.tsx,sha256=Yg6FG0LtrneRfAYeBu3crt_RdN-_o7FojtYhDMDKi0o,8595
@@ -149,14 +150,19 @@ lyrics_transcriber/frontend/src/components/EditTimelineSection.tsx,sha256=VQy5fp
149
150
  lyrics_transcriber/frontend/src/components/EditWordList.tsx,sha256=atl-9Z-24U-KWojwo0apTy1Y9DbQGoVo2dFX4P-1Z9E,13681
150
151
  lyrics_transcriber/frontend/src/components/FileUpload.tsx,sha256=fwn2rMWtMLPTZLREMb3ps4prSf9nzxGwnjmeC6KYsJA,2383
151
152
  lyrics_transcriber/frontend/src/components/FindReplaceModal.tsx,sha256=U7duKns4IqNXwbWFbQfdyaswnvkSRpfsU0UG__-Serc,20192
152
- lyrics_transcriber/frontend/src/components/Header.tsx,sha256=NaG4jsKYqZ1G8pmltFbE_NLncyA4ClbfCvpOTC4MQR0,17312
153
- lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx,sha256=NyM32pI9nNzDbRa6p-r30upJcolqQRcN_YNd5v-nVxA,53644
153
+ lyrics_transcriber/frontend/src/components/Header.tsx,sha256=KE8TirDEUgojgHjP9R4HTtCxaVr_CWumky-Es7xAGsE,18754
154
+ lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx,sha256=TFo2HCr6k9bENsQm-AfT797ZyFLqvKDa8g5W-pw1v24,54256
155
+ lyrics_transcriber/frontend/src/components/LyricsSynchronizer/SyncControls.tsx,sha256=j4rQjBQVbaPsp1ra_rvEoCqmX3JFJdfNnFvj3BvfsgQ,6069
156
+ lyrics_transcriber/frontend/src/components/LyricsSynchronizer/TimelineCanvas.tsx,sha256=h-sAfDFm-DfayZbKuNJmbAqxmMr89oC26xI4WB45bxA,26896
157
+ lyrics_transcriber/frontend/src/components/LyricsSynchronizer/UpcomingWordsBar.tsx,sha256=BXkEeo5yMgHkeOCBcZKqxMb1rspjXH-X5_6X9Hl7z3E,2588
158
+ lyrics_transcriber/frontend/src/components/LyricsSynchronizer/index.tsx,sha256=jOAmlkodaGzceQLG11ihprFMcARVbeJ_qjMcUkQj5Oo,34275
154
159
  lyrics_transcriber/frontend/src/components/MetricsDashboard.tsx,sha256=33XpyHj0siBQivE8vLgliyiwmdsAfnNQh5Py4mnhXi8,1724
160
+ lyrics_transcriber/frontend/src/components/ModeSelectionModal.tsx,sha256=eihGI49r9tKq-AaEtnmVrbiBOoJApWvabaZW4ydmg-4,5302
155
161
  lyrics_transcriber/frontend/src/components/ModeSelector.tsx,sha256=HnBAK_gFgNBJLtMC_ESMVdUapDjmqmoLX8pQeyHfpOw,2651
156
162
  lyrics_transcriber/frontend/src/components/ModelSelector.tsx,sha256=lfG_B5VAzSfrU0FqJl8XptN6DVt2kSljU96HMXo8mf4,559
157
163
  lyrics_transcriber/frontend/src/components/PreviewVideoSection.tsx,sha256=59ZhG5XsxUZ_dkK8BjTQhYmYP5Wv86uRR-xtuwFRK8c,5582
158
164
  lyrics_transcriber/frontend/src/components/ReferenceView.tsx,sha256=2ugpkVtxZLS6Al-lmYbMZj7d3w9e2qNNYKJjkSxZ_X8,10494
159
- lyrics_transcriber/frontend/src/components/ReplaceAllLyricsModal.tsx,sha256=brGNO-q9Sir0MlsnJIWTlBk_FaYk30f-0CH2z04-K88,26555
165
+ lyrics_transcriber/frontend/src/components/ReplaceAllLyricsModal.tsx,sha256=pVlqHrSloxXZV_Ib8cbk1invF7WA3uge5b7pnFPe9Pc,12290
160
166
  lyrics_transcriber/frontend/src/components/ReviewChangesModal.tsx,sha256=VQg_gBFViAxQu9Z75o6rOsvmH5DZBjKq9FkU8aB_7mI,13790
161
167
  lyrics_transcriber/frontend/src/components/SegmentDetailsModal.tsx,sha256=6ME02FkFwCgDAxW49yW260N4vbr80eAJ332Ex811GOo,1643
162
168
  lyrics_transcriber/frontend/src/components/TimelineEditor.tsx,sha256=gJRCxdmJo80g0h5hq5AtDHK-HbOoYhMaQYvP2WgOuRI,13201
@@ -188,7 +194,7 @@ lyrics_transcriber/frontend/src/vite-env.d.ts,sha256=ZZlpNvuwQpFfe3SiAPzd5-QQ8yp
188
194
  lyrics_transcriber/frontend/tsconfig.app.json,sha256=7aUBVcaBqEtmtfQXsbwsgBxSUng06xzQi5t4QCgWQ3E,665
189
195
  lyrics_transcriber/frontend/tsconfig.json,sha256=AOS5v1AsNPL3wGc8bt58Ybh8HHpbYrlK91q0KIzaSgs,627
190
196
  lyrics_transcriber/frontend/tsconfig.node.json,sha256=oMBhK5xufBrVE7SkbADRxA3pxm8_L9m5YwtCOZSafsc,536
191
- lyrics_transcriber/frontend/tsconfig.tsbuildinfo,sha256=NadSnxLYpHcjqlB_pXw5XD4J8ml7QlaHSilBz_ksUo8,1987
197
+ lyrics_transcriber/frontend/tsconfig.tsbuildinfo,sha256=TGnTUZG3ScMXJTHpm1GmyxtEte94DRXNrmWx6-VlB9M,2248
192
198
  lyrics_transcriber/frontend/update_version.js,sha256=PxkqCnsucXnXiIqutsanVcx00Gq4k7pgCYj_uXCa4qw,411
193
199
  lyrics_transcriber/frontend/vite.config.d.ts,sha256=S5bdGf0pSdKM6A6RNBKwAm3EIeW_bDHYfHtesRtXU7Q,76
194
200
  lyrics_transcriber/frontend/vite.config.js,sha256=P4GuPgRZzwEWPQZpyujUe7eA3mjPoFAe2CgE5sQAXg8,232
@@ -196,12 +202,12 @@ lyrics_transcriber/frontend/vite.config.ts,sha256=8FdW0dN8zDFqfhQSxX5h7sIu72X2pi
196
202
  lyrics_transcriber/frontend/web_assets/android-chrome-192x192.png,sha256=lg-6aPF5mGLiuG7LyftZk_0RI41srmpA8wj-NkaaQms,17632
197
203
  lyrics_transcriber/frontend/web_assets/android-chrome-512x512.png,sha256=x-zuKT3NYsTqAWzhKRTZeD4-0uYoUjqMPZpKTChqNJ8,123447
198
204
  lyrics_transcriber/frontend/web_assets/apple-touch-icon.png,sha256=6y5vGra54w5oc8VP6sn2JjoQtN9hWTKn0YPhmdlmfU0,16188
199
- lyrics_transcriber/frontend/web_assets/assets/index-DdJTDWH3.js,sha256=bAn-S8FSKU7tZBt0duA4-X5BO3YLwUrhXWnf974XpCs,1370446
200
- lyrics_transcriber/frontend/web_assets/assets/index-DdJTDWH3.js.map,sha256=MWXeja7_HjwkNk8woEgiSm68lStu10UHcljswi9YMXI,2905511
205
+ lyrics_transcriber/frontend/web_assets/assets/index-BECn1o8Q.js,sha256=yNW-EWYuyaj_16fGDyvCGkb2BGoCufHlpKquqOxzQac,1421945
206
+ lyrics_transcriber/frontend/web_assets/assets/index-BECn1o8Q.js.map,sha256=aY17CfmBvSG6FgnuQLzgc9V02xfaOimIIAo8l4fiAJk,3004853
201
207
  lyrics_transcriber/frontend/web_assets/favicon-16x16.png,sha256=2wy_7ZmVS4d7umByJVHQ37DBB_8xrU9xfT1_konSXQI,604
202
208
  lyrics_transcriber/frontend/web_assets/favicon-32x32.png,sha256=6TyrRMIw6G8dpG4_QWVTY8DMxo0bIGzc_9aOJrkiJKM,1297
203
209
  lyrics_transcriber/frontend/web_assets/favicon.ico,sha256=ZK7QvdBuZp0QxPkluCW4IKxfleFmFLp1KkG_d5xmx7E,15406
204
- lyrics_transcriber/frontend/web_assets/index.html,sha256=2WGlcSZoJwyjbJVe93ZpikwRLJiaEJV3ZrLI_ZjejH4,830
210
+ lyrics_transcriber/frontend/web_assets/index.html,sha256=qqHYlM3VAy_3YJn3QgETGjR94XTWL3Ci_S-5_lHYgxw,830
205
211
  lyrics_transcriber/frontend/web_assets/nomad-karaoke-logo.png,sha256=jTTBFXV6hGJGolZYQ-dIjgQQbMsehk5XGtsllhLrdzg,212641
206
212
  lyrics_transcriber/frontend/yarn.lock,sha256=wtImLsCO1P1Lpkhc1jAN6IiHQ0As4xn39n0cwKoh4LM,131996
207
213
  lyrics_transcriber/lyrics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -247,7 +253,7 @@ lyrics_transcriber/output/cdgmaker/transitions/wipeleft.png,sha256=7jY6Z-mdY94PE
247
253
  lyrics_transcriber/output/cdgmaker/transitions/wipeout.png,sha256=YSLdv2RCGeSilOGt7PYTLAOlBfL4bofu8Vpj-PcskZo,9995
248
254
  lyrics_transcriber/output/cdgmaker/transitions/wiperight.png,sha256=Dw3N6cqJaSwL3owDqyAM4W573EmdNnkjdiXhoxWC4yk,8443
249
255
  lyrics_transcriber/output/cdgmaker/utils.py,sha256=TNcOnccegpxT3OogWQCexy8BrDqfihLAShMvqeM81cw,2945
250
- lyrics_transcriber/output/countdown_processor.py,sha256=i2j0gSxAfgGPxdKnr3d_Vkzt-CXW-BALJDmOQ13dfBY,9457
256
+ lyrics_transcriber/output/countdown_processor.py,sha256=EsUmnamNiG3SRfqPsZjSH3BH81a9AUg7wIdtfDkW57s,10848
251
257
  lyrics_transcriber/output/fonts/AvenirNext-Bold.ttf,sha256=YxgKz2OP46lwLPCpIZhVa8COi_9KRDSXw4n8dIHHQSs,327048
252
258
  "lyrics_transcriber/output/fonts/DMSans-VariableFont_opsz,wght.ttf",sha256=7uav75vmxRukpMx8wqtPeNvaxqOzlBljO400geBzYYI,238984
253
259
  lyrics_transcriber/output/fonts/DMSerifDisplay-Regular.ttf,sha256=mMHMLr3CMjEQgJ5cKlYEn8YSsHwSnDtxT-Qjn_n8ffM,72220
@@ -264,18 +270,18 @@ lyrics_transcriber/output/segment_resizer.py,sha256=rrgcQC28eExSAmGnm6ytkF-E-nH4
264
270
  lyrics_transcriber/output/subtitles.py,sha256=TW8IFTedj7-jHDyKKLYk1rFSFmuk8qysrI83Lkc3x-o,19555
265
271
  lyrics_transcriber/output/video.py,sha256=n6QtT3ljtx1t8CT9jmVTKSpdezF2gC0FJsYDDtkP5fE,24084
266
272
  lyrics_transcriber/review/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
267
- lyrics_transcriber/review/server.py,sha256=I4HTMif2F1bGu_7FLEdFQ-656JToOA9TWARKMARMmp0,29430
273
+ lyrics_transcriber/review/server.py,sha256=_05ul0MsddKf8iTIg-NASVYkl9kBRo0M3WhWBxp79i8,29462
268
274
  lyrics_transcriber/storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
269
275
  lyrics_transcriber/storage/dropbox.py,sha256=Dyam1ULTkoxD1X5trkZ5dGp5XhBGCn998moC8IS9-68,9804
270
276
  lyrics_transcriber/transcribers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
271
- lyrics_transcriber/transcribers/audioshake.py,sha256=4VQdm-r3Pe0nIlJzqgOyOSp7amnWnU05VhFxBidY_WU,11831
277
+ lyrics_transcriber/transcribers/audioshake.py,sha256=RihuLKzhhHfX7m5cjKISwIuTQkGWapCS29D6Qk3hR4U,15869
272
278
  lyrics_transcriber/transcribers/base_transcriber.py,sha256=T3m4ZCwZ9Bpv6Jvb2hNcnllk-lmeNmADDJlSySBtP1Q,6480
273
279
  lyrics_transcriber/transcribers/whisper.py,sha256=YcCB1ic9H6zL1GS0jD0emu8-qlcH0QVEjjjYB4aLlIQ,13260
274
- lyrics_transcriber/types.py,sha256=Y7WUx8PAOBYWCIZgw4ndeHfPH8Gg--O3OYYQgMpJ2iI,27728
280
+ lyrics_transcriber/types.py,sha256=UJjaxhVd2o14AG4G8ToU598p0JeYdiTFjpG38jGCoYQ,27917
275
281
  lyrics_transcriber/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
276
282
  lyrics_transcriber/utils/word_utils.py,sha256=-cMGpj9UV4F6IsoDKAV2i1aiqSO8eI91HMAm_igtVMk,958
277
- karaoke_gen-0.71.42.dist-info/METADATA,sha256=5VKGb-ifzRJt1HUujSXMo3hDgVqg-f7qW7xg3Kb-i80,17071
278
- karaoke_gen-0.71.42.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
279
- karaoke_gen-0.71.42.dist-info/entry_points.txt,sha256=xIyLe7K84ZyjO8L0_AmNectz93QjGSs5AkApMtlAd4g,160
280
- karaoke_gen-0.71.42.dist-info/licenses/LICENSE,sha256=81R_4XwMZDODHD7JcZeUR8IiCU8AD7Ajl6bmwR9tYDk,1074
281
- karaoke_gen-0.71.42.dist-info/RECORD,,
283
+ karaoke_gen-0.75.53.dist-info/METADATA,sha256=DR2eCSIs1DM9Xudkimpy66AgxZRfYSYyzpBX-sxkhgE,20662
284
+ karaoke_gen-0.75.53.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
285
+ karaoke_gen-0.75.53.dist-info/entry_points.txt,sha256=xIyLe7K84ZyjO8L0_AmNectz93QjGSs5AkApMtlAd4g,160
286
+ karaoke_gen-0.75.53.dist-info/licenses/LICENSE,sha256=81R_4XwMZDODHD7JcZeUR8IiCU8AD7Ajl6bmwR9tYDk,1074
287
+ karaoke_gen-0.75.53.dist-info/RECORD,,
@@ -307,6 +307,37 @@ class LyricsTranscriber:
307
307
  self.results.transcription_corrected = CorrectionResult.from_dict(corrections_data)
308
308
  self.logger.info("Successfully loaded existing corrections data")
309
309
 
310
+ # Check if the loaded corrections have countdown padding applied
311
+ # This is important because the video needs to use padded audio to sync
312
+ # with the countdown-adjusted timestamps in the ASS subtitles
313
+ if self.output_config.add_countdown:
314
+ from lyrics_transcriber.output.countdown_processor import CountdownProcessor
315
+
316
+ countdown_processor = CountdownProcessor(
317
+ cache_dir=self.output_config.cache_dir,
318
+ logger=self.logger,
319
+ )
320
+
321
+ if countdown_processor.has_countdown(self.results.transcription_corrected):
322
+ self.logger.info(
323
+ "Loaded corrections have countdown - creating padded audio for video sync"
324
+ )
325
+ # Create padded audio file to match the countdown-adjusted timestamps
326
+ padded_audio_path = countdown_processor.create_padded_audio_only(self.audio_filepath)
327
+ self.audio_filepath = padded_audio_path
328
+
329
+ # Set countdown padding attributes on results
330
+ self.results.countdown_padding_added = True
331
+ self.results.countdown_padding_seconds = countdown_processor.COUNTDOWN_PADDING_SECONDS
332
+ self.results.padded_audio_filepath = padded_audio_path
333
+
334
+ self.logger.info(
335
+ f"Countdown padding applied: {countdown_processor.COUNTDOWN_PADDING_SECONDS}s. "
336
+ f"Using padded audio: {padded_audio_path}"
337
+ )
338
+ else:
339
+ self.logger.info("Loaded corrections do not have countdown - no padding needed")
340
+
310
341
  # Skip to output generation
311
342
  self.generate_outputs()
312
343
  self.logger.info("Processing completed successfully using existing corrections")
@@ -363,7 +394,24 @@ class LyricsTranscriber:
363
394
 
364
395
  def transcribe(self) -> None:
365
396
  """Run transcription using all available transcribers."""
366
- self.logger.info(f"Starting transcription with providers: {list(self.transcribers.keys())}")
397
+ provider_names = list(self.transcribers.keys())
398
+
399
+ if not provider_names:
400
+ self.logger.warning(
401
+ "Starting transcription with providers: [] - NO TRANSCRIPTION PROVIDERS CONFIGURED!\n"
402
+ "\n"
403
+ "This means no word-level timing data will be generated, and synchronized karaoke "
404
+ "lyrics cannot be created. The output will lack the '(With Vocals).mkv' video file.\n"
405
+ "\n"
406
+ "To enable transcription, configure at least one provider:\n"
407
+ " - AudioShake: Set AUDIOSHAKE_API_TOKEN environment variable\n"
408
+ " - Whisper/RunPod: Set RUNPOD_API_KEY and WHISPER_RUNPOD_ID environment variables\n"
409
+ "\n"
410
+ "See README.md 'Transcription Providers' section for detailed setup instructions."
411
+ )
412
+ else:
413
+ self.logger.info(f"Starting transcription with providers: {provider_names}")
414
+ self._log_provider_configuration_status()
367
415
 
368
416
  for name, transcriber_info in self.transcribers.items():
369
417
  self.logger.info(f"Running transcription with {name}")
@@ -376,7 +424,33 @@ class LyricsTranscriber:
376
424
  self.logger.debug(f"Transcription completed for {name}")
377
425
 
378
426
  if not self.results.transcription_results:
379
- self.logger.warning("No successful transcriptions from any provider")
427
+ self.logger.warning(
428
+ "No successful transcriptions from any provider. "
429
+ "Check that your API tokens are valid and the services are accessible."
430
+ )
431
+
432
+ def _log_provider_configuration_status(self) -> None:
433
+ """Log detailed configuration status for each potential transcription provider."""
434
+ self.logger.debug("Transcription provider configuration status:")
435
+
436
+ # AudioShake status
437
+ if self.transcriber_config.audioshake_api_token:
438
+ self.logger.debug(" - AudioShake: CONFIGURED (API token provided)")
439
+ else:
440
+ self.logger.debug(" - AudioShake: NOT CONFIGURED (missing AUDIOSHAKE_API_TOKEN)")
441
+
442
+ # Whisper/RunPod status
443
+ has_runpod_key = bool(self.transcriber_config.runpod_api_key)
444
+ has_whisper_id = bool(self.transcriber_config.whisper_runpod_id)
445
+
446
+ if has_runpod_key and has_whisper_id:
447
+ self.logger.debug(" - Whisper (RunPod): CONFIGURED (API key and endpoint ID provided)")
448
+ elif has_runpod_key:
449
+ self.logger.debug(" - Whisper (RunPod): PARTIALLY CONFIGURED (missing WHISPER_RUNPOD_ID)")
450
+ elif has_whisper_id:
451
+ self.logger.debug(" - Whisper (RunPod): PARTIALLY CONFIGURED (missing RUNPOD_API_KEY)")
452
+ else:
453
+ self.logger.debug(" - Whisper (RunPod): NOT CONFIGURED (missing RUNPOD_API_KEY and WHISPER_RUNPOD_ID)")
380
454
 
381
455
  def correct_lyrics(self) -> None:
382
456
  """Run lyrics correction using transcription and internet lyrics."""
@@ -2,7 +2,7 @@
2
2
  "name": "lyrics-transcriber-frontend",
3
3
  "private": true,
4
4
  "homepage": "https://nomadkaraoke.github.io/lyrics-transcriber-frontend",
5
- "version": "0.80.0",
5
+ "version": "0.83.0",
6
6
  "type": "module",
7
7
  "scripts": {
8
8
  "dev": "vite",
@@ -19,25 +19,27 @@ export default function App() {
19
19
  const params = new URLSearchParams(window.location.search)
20
20
  const encodedApiUrl = params.get('baseApiUrl')
21
21
  const audioHashParam = params.get('audioHash')
22
+ const reviewTokenParam = params.get('reviewToken')
22
23
 
23
24
  if (encodedApiUrl) {
24
25
  const baseApiUrl = decodeURIComponent(encodedApiUrl)
25
- setApiClient(new LiveApiClient(baseApiUrl))
26
+ // Pass reviewToken to LiveApiClient for authentication
27
+ setApiClient(new LiveApiClient(baseApiUrl, reviewTokenParam || undefined))
26
28
  setIsReadOnly(false)
27
29
  if (audioHashParam) {
28
30
  setAudioHash(audioHashParam)
29
31
  }
30
32
  // Fetch initial data
31
- fetchData(baseApiUrl)
33
+ fetchData(baseApiUrl, reviewTokenParam || undefined)
32
34
  } else {
33
35
  setApiClient(new FileOnlyClient())
34
36
  setIsReadOnly(true)
35
37
  }
36
38
  }, [])
37
39
 
38
- const fetchData = async (baseUrl: string) => {
40
+ const fetchData = async (baseUrl: string, reviewToken?: string) => {
39
41
  try {
40
- const client = new LiveApiClient(baseUrl)
42
+ const client = new LiveApiClient(baseUrl, reviewToken)
41
43
  const data = await client.getCorrectionData()
42
44
  // console.log('Full correction data from API:', data)
43
45
  setData(data)
@@ -35,14 +35,29 @@ interface AddLyricsRequest {
35
35
  }
36
36
 
37
37
  export class LiveApiClient implements ApiClient {
38
- constructor(private baseUrl: string) {
38
+ private reviewToken?: string;
39
+
40
+ constructor(private baseUrl: string, reviewToken?: string) {
39
41
  this.baseUrl = baseUrl.replace(/\/$/, '')
42
+ this.reviewToken = reviewToken
40
43
  }
41
44
 
42
45
  public isUpdatingHandlers = false;
43
46
 
47
+ /**
48
+ * Build URL with reviewToken query parameter if available
49
+ */
50
+ private buildUrl(path: string): string {
51
+ const url = `${this.baseUrl}${path}`
52
+ if (this.reviewToken) {
53
+ const separator = url.includes('?') ? '&' : '?'
54
+ return `${url}${separator}review_token=${encodeURIComponent(this.reviewToken)}`
55
+ }
56
+ return url
57
+ }
58
+
44
59
  async getCorrectionData(): Promise<CorrectionData> {
45
- const response = await fetch(`${this.baseUrl}/correction-data`);
60
+ const response = await fetch(this.buildUrl('/correction-data'));
46
61
  if (!response.ok) {
47
62
  throw new Error(`API error: ${response.statusText}`);
48
63
  }
@@ -64,7 +79,7 @@ export class LiveApiClient implements ApiClient {
64
79
  corrected_segments: data.corrected_segments
65
80
  };
66
81
 
67
- const response = await fetch(`${this.baseUrl}/complete`, {
82
+ const response = await fetch(this.buildUrl('/complete'), {
68
83
  method: 'POST',
69
84
  headers: {
70
85
  'Content-Type': 'application/json',
@@ -78,7 +93,7 @@ export class LiveApiClient implements ApiClient {
78
93
  }
79
94
 
80
95
  getAudioUrl(audioHash: string): string {
81
- return `${this.baseUrl}/audio/${audioHash}`
96
+ return this.buildUrl(`/audio/${audioHash}`)
82
97
  }
83
98
 
84
99
  async generatePreviewVideo(data: CorrectionData): Promise<PreviewVideoResponse> {
@@ -88,7 +103,7 @@ export class LiveApiClient implements ApiClient {
88
103
  corrected_segments: data.corrected_segments
89
104
  };
90
105
 
91
- const response = await fetch(`${this.baseUrl}/preview-video`, {
106
+ const response = await fetch(this.buildUrl('/preview-video'), {
92
107
  method: 'POST',
93
108
  headers: {
94
109
  'Content-Type': 'application/json',
@@ -107,7 +122,7 @@ export class LiveApiClient implements ApiClient {
107
122
  }
108
123
 
109
124
  getPreviewVideoUrl(previewHash: string): string {
110
- return `${this.baseUrl}/preview-video/${previewHash}`;
125
+ return this.buildUrl(`/preview-video/${previewHash}`);
111
126
  }
112
127
 
113
128
  async updateHandlers(enabledHandlers: string[]): Promise<CorrectionData> {
@@ -116,7 +131,7 @@ export class LiveApiClient implements ApiClient {
116
131
  console.log('API: Set isUpdatingHandlers to', this.isUpdatingHandlers);
117
132
 
118
133
  try {
119
- const response = await fetch(`${this.baseUrl}/handlers`, {
134
+ const response = await fetch(this.buildUrl('/handlers'), {
120
135
  method: 'POST',
121
136
  headers: {
122
137
  'Content-Type': 'application/json',
@@ -147,7 +162,7 @@ export class LiveApiClient implements ApiClient {
147
162
  lyrics
148
163
  };
149
164
 
150
- const response = await fetch(`${this.baseUrl}/add-lyrics`, {
165
+ const response = await fetch(this.buildUrl('/add-lyrics'), {
151
166
  method: 'POST',
152
167
  headers: {
153
168
  'Content-Type': 'application/json',
@@ -170,7 +185,7 @@ export class LiveApiClient implements ApiClient {
170
185
  async submitAnnotations(annotations: Omit<CorrectionAnnotation, 'annotation_id' | 'timestamp'>[]): Promise<void> {
171
186
  // Submit each annotation to the backend
172
187
  for (const annotation of annotations) {
173
- const response = await fetch(`${this.baseUrl}/v1/annotations`, {
188
+ const response = await fetch(this.buildUrl('/v1/annotations'), {
174
189
  method: 'POST',
175
190
  headers: {
176
191
  'Content-Type': 'application/json',
@@ -186,7 +201,7 @@ export class LiveApiClient implements ApiClient {
186
201
  }
187
202
 
188
203
  async getAnnotationStats(): Promise<any> {
189
- const response = await fetch(`${this.baseUrl}/v1/annotations/stats`);
204
+ const response = await fetch(this.buildUrl('/v1/annotations/stats'));
190
205
  if (!response.ok) {
191
206
  throw new Error(`API error: ${response.statusText}`);
192
207
  }
@@ -1,4 +1,4 @@
1
- import { Box, Button, Typography, useMediaQuery, useTheme, Switch, FormControlLabel, Tooltip, Paper, IconButton } from '@mui/material'
1
+ import { Box, Button, Typography, useMediaQuery, useTheme, Switch, FormControlLabel, Tooltip, Paper, IconButton, Chip } from '@mui/material'
2
2
  import LockIcon from '@mui/icons-material/Lock'
3
3
  import UploadFileIcon from '@mui/icons-material/UploadFile'
4
4
  import FindReplaceIcon from '@mui/icons-material/FindReplace'
@@ -7,6 +7,7 @@ import UndoIcon from '@mui/icons-material/Undo'
7
7
  import RedoIcon from '@mui/icons-material/Redo'
8
8
  import TimerIcon from '@mui/icons-material/Timer'
9
9
  import RestoreIcon from '@mui/icons-material/Restore'
10
+ import RateReviewIcon from '@mui/icons-material/RateReview'
10
11
  import { CorrectionData, InteractionMode } from '../types'
11
12
  import CorrectionMetrics from './CorrectionMetrics'
12
13
  import AgenticCorrectionMetrics from './AgenticCorrectionMetrics'
@@ -41,6 +42,8 @@ interface HeaderProps {
41
42
  onRedo: () => void
42
43
  canUndo: boolean
43
44
  canRedo: boolean
45
+ annotationsEnabled?: boolean
46
+ onAnnotationsToggle?: (enabled: boolean) => void
44
47
  }
45
48
 
46
49
  export default function Header({
@@ -65,6 +68,8 @@ export default function Header({
65
68
  onRedo,
66
69
  canUndo,
67
70
  canRedo,
71
+ annotationsEnabled = true,
72
+ onAnnotationsToggle,
68
73
  }: HeaderProps) {
69
74
  const theme = useTheme()
70
75
  const isMobile = useMediaQuery(theme.breakpoints.down('md'))
@@ -135,17 +140,38 @@ export default function Header({
135
140
  <Typography variant="h4" sx={{ fontSize: isMobile ? '1.3rem' : '1.5rem' }}>
136
141
  Nomad Karaoke: Lyrics Transcription Review
137
142
  </Typography>
138
- {isReadOnly && (
139
- <Button
140
- variant="outlined"
141
- size="small"
142
- startIcon={<UploadFileIcon />}
143
- onClick={onFileLoad}
144
- fullWidth={isMobile}
145
- >
146
- Load File
147
- </Button>
148
- )}
143
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
144
+ {!isReadOnly && onAnnotationsToggle && (
145
+ <Tooltip title={annotationsEnabled
146
+ ? "Click to disable annotation prompts when editing"
147
+ : "Click to enable annotation prompts when editing"
148
+ }>
149
+ <Chip
150
+ icon={<RateReviewIcon />}
151
+ label={annotationsEnabled ? "Feedback On" : "Feedback Off"}
152
+ onClick={() => onAnnotationsToggle(!annotationsEnabled)}
153
+ color={annotationsEnabled ? "primary" : "default"}
154
+ variant={annotationsEnabled ? "filled" : "outlined"}
155
+ size="small"
156
+ sx={{
157
+ cursor: 'pointer',
158
+ '& .MuiChip-icon': { fontSize: '1rem' }
159
+ }}
160
+ />
161
+ </Tooltip>
162
+ )}
163
+ {isReadOnly && (
164
+ <Button
165
+ variant="outlined"
166
+ size="small"
167
+ startIcon={<UploadFileIcon />}
168
+ onClick={onFileLoad}
169
+ fullWidth={isMobile}
170
+ >
171
+ Load File
172
+ </Button>
173
+ )}
174
+ </Box>
149
175
  </Box>
150
176
 
151
177
  <Box sx={{