karaoke-gen 0.86.7__py3-none-any.whl → 0.96.0__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 (188) hide show
  1. backend/.coveragerc +20 -0
  2. backend/.gitignore +37 -0
  3. backend/Dockerfile +43 -0
  4. backend/Dockerfile.base +74 -0
  5. backend/README.md +242 -0
  6. backend/__init__.py +0 -0
  7. backend/api/__init__.py +0 -0
  8. backend/api/dependencies.py +457 -0
  9. backend/api/routes/__init__.py +0 -0
  10. backend/api/routes/admin.py +742 -0
  11. backend/api/routes/audio_search.py +903 -0
  12. backend/api/routes/auth.py +348 -0
  13. backend/api/routes/file_upload.py +2076 -0
  14. backend/api/routes/health.py +344 -0
  15. backend/api/routes/internal.py +435 -0
  16. backend/api/routes/jobs.py +1610 -0
  17. backend/api/routes/review.py +652 -0
  18. backend/api/routes/themes.py +162 -0
  19. backend/api/routes/users.py +1014 -0
  20. backend/config.py +172 -0
  21. backend/main.py +133 -0
  22. backend/middleware/__init__.py +5 -0
  23. backend/middleware/audit_logging.py +124 -0
  24. backend/models/__init__.py +0 -0
  25. backend/models/job.py +519 -0
  26. backend/models/requests.py +123 -0
  27. backend/models/theme.py +153 -0
  28. backend/models/user.py +254 -0
  29. backend/models/worker_log.py +164 -0
  30. backend/pyproject.toml +29 -0
  31. backend/quick-check.sh +93 -0
  32. backend/requirements.txt +29 -0
  33. backend/run_tests.sh +60 -0
  34. backend/services/__init__.py +0 -0
  35. backend/services/audio_analysis_service.py +243 -0
  36. backend/services/audio_editing_service.py +278 -0
  37. backend/services/audio_search_service.py +702 -0
  38. backend/services/auth_service.py +630 -0
  39. backend/services/credential_manager.py +792 -0
  40. backend/services/discord_service.py +172 -0
  41. backend/services/dropbox_service.py +301 -0
  42. backend/services/email_service.py +1093 -0
  43. backend/services/encoding_interface.py +454 -0
  44. backend/services/encoding_service.py +405 -0
  45. backend/services/firestore_service.py +512 -0
  46. backend/services/flacfetch_client.py +573 -0
  47. backend/services/gce_encoding/README.md +72 -0
  48. backend/services/gce_encoding/__init__.py +22 -0
  49. backend/services/gce_encoding/main.py +589 -0
  50. backend/services/gce_encoding/requirements.txt +16 -0
  51. backend/services/gdrive_service.py +356 -0
  52. backend/services/job_logging.py +258 -0
  53. backend/services/job_manager.py +842 -0
  54. backend/services/job_notification_service.py +271 -0
  55. backend/services/local_encoding_service.py +590 -0
  56. backend/services/local_preview_encoding_service.py +407 -0
  57. backend/services/lyrics_cache_service.py +216 -0
  58. backend/services/metrics.py +413 -0
  59. backend/services/packaging_service.py +287 -0
  60. backend/services/rclone_service.py +106 -0
  61. backend/services/storage_service.py +209 -0
  62. backend/services/stripe_service.py +275 -0
  63. backend/services/structured_logging.py +254 -0
  64. backend/services/template_service.py +330 -0
  65. backend/services/theme_service.py +469 -0
  66. backend/services/tracing.py +543 -0
  67. backend/services/user_service.py +721 -0
  68. backend/services/worker_service.py +558 -0
  69. backend/services/youtube_service.py +112 -0
  70. backend/services/youtube_upload_service.py +445 -0
  71. backend/tests/__init__.py +4 -0
  72. backend/tests/conftest.py +224 -0
  73. backend/tests/emulator/__init__.py +7 -0
  74. backend/tests/emulator/conftest.py +88 -0
  75. backend/tests/emulator/test_e2e_cli_backend.py +1053 -0
  76. backend/tests/emulator/test_emulator_integration.py +356 -0
  77. backend/tests/emulator/test_style_loading_direct.py +436 -0
  78. backend/tests/emulator/test_worker_logs_direct.py +229 -0
  79. backend/tests/emulator/test_worker_logs_subcollection.py +443 -0
  80. backend/tests/requirements-test.txt +10 -0
  81. backend/tests/requirements.txt +6 -0
  82. backend/tests/test_admin_email_endpoints.py +411 -0
  83. backend/tests/test_api_integration.py +460 -0
  84. backend/tests/test_api_routes.py +93 -0
  85. backend/tests/test_audio_analysis_service.py +294 -0
  86. backend/tests/test_audio_editing_service.py +386 -0
  87. backend/tests/test_audio_search.py +1398 -0
  88. backend/tests/test_audio_services.py +378 -0
  89. backend/tests/test_auth_firestore.py +231 -0
  90. backend/tests/test_config_extended.py +68 -0
  91. backend/tests/test_credential_manager.py +377 -0
  92. backend/tests/test_dependencies.py +54 -0
  93. backend/tests/test_discord_service.py +244 -0
  94. backend/tests/test_distribution_services.py +820 -0
  95. backend/tests/test_dropbox_service.py +472 -0
  96. backend/tests/test_email_service.py +492 -0
  97. backend/tests/test_emulator_integration.py +322 -0
  98. backend/tests/test_encoding_interface.py +412 -0
  99. backend/tests/test_file_upload.py +1739 -0
  100. backend/tests/test_flacfetch_client.py +632 -0
  101. backend/tests/test_gdrive_service.py +524 -0
  102. backend/tests/test_instrumental_api.py +431 -0
  103. backend/tests/test_internal_api.py +343 -0
  104. backend/tests/test_job_creation_regression.py +583 -0
  105. backend/tests/test_job_manager.py +339 -0
  106. backend/tests/test_job_manager_notifications.py +329 -0
  107. backend/tests/test_job_notification_service.py +443 -0
  108. backend/tests/test_jobs_api.py +273 -0
  109. backend/tests/test_local_encoding_service.py +423 -0
  110. backend/tests/test_local_preview_encoding_service.py +567 -0
  111. backend/tests/test_main.py +87 -0
  112. backend/tests/test_models.py +918 -0
  113. backend/tests/test_packaging_service.py +382 -0
  114. backend/tests/test_requests.py +201 -0
  115. backend/tests/test_routes_jobs.py +282 -0
  116. backend/tests/test_routes_review.py +337 -0
  117. backend/tests/test_services.py +556 -0
  118. backend/tests/test_services_extended.py +112 -0
  119. backend/tests/test_storage_service.py +448 -0
  120. backend/tests/test_style_upload.py +261 -0
  121. backend/tests/test_template_service.py +295 -0
  122. backend/tests/test_theme_service.py +516 -0
  123. backend/tests/test_unicode_sanitization.py +522 -0
  124. backend/tests/test_upload_api.py +256 -0
  125. backend/tests/test_validate.py +156 -0
  126. backend/tests/test_video_worker_orchestrator.py +847 -0
  127. backend/tests/test_worker_log_subcollection.py +509 -0
  128. backend/tests/test_worker_logging.py +365 -0
  129. backend/tests/test_workers.py +1116 -0
  130. backend/tests/test_workers_extended.py +178 -0
  131. backend/tests/test_youtube_service.py +247 -0
  132. backend/tests/test_youtube_upload_service.py +568 -0
  133. backend/validate.py +173 -0
  134. backend/version.py +27 -0
  135. backend/workers/README.md +597 -0
  136. backend/workers/__init__.py +11 -0
  137. backend/workers/audio_worker.py +618 -0
  138. backend/workers/lyrics_worker.py +683 -0
  139. backend/workers/render_video_worker.py +483 -0
  140. backend/workers/screens_worker.py +525 -0
  141. backend/workers/style_helper.py +198 -0
  142. backend/workers/video_worker.py +1277 -0
  143. backend/workers/video_worker_orchestrator.py +701 -0
  144. backend/workers/worker_logging.py +278 -0
  145. karaoke_gen/instrumental_review/static/index.html +7 -4
  146. karaoke_gen/karaoke_finalise/karaoke_finalise.py +6 -1
  147. karaoke_gen/style_loader.py +3 -1
  148. karaoke_gen/utils/__init__.py +163 -8
  149. karaoke_gen/video_background_processor.py +9 -4
  150. {karaoke_gen-0.86.7.dist-info → karaoke_gen-0.96.0.dist-info}/METADATA +2 -1
  151. {karaoke_gen-0.86.7.dist-info → karaoke_gen-0.96.0.dist-info}/RECORD +187 -42
  152. lyrics_transcriber/correction/agentic/providers/config.py +9 -5
  153. lyrics_transcriber/correction/agentic/providers/langchain_bridge.py +1 -51
  154. lyrics_transcriber/correction/corrector.py +192 -130
  155. lyrics_transcriber/correction/operations.py +24 -9
  156. lyrics_transcriber/frontend/package-lock.json +2 -2
  157. lyrics_transcriber/frontend/package.json +1 -1
  158. lyrics_transcriber/frontend/src/components/AIFeedbackModal.tsx +1 -1
  159. lyrics_transcriber/frontend/src/components/CorrectedWordWithActions.tsx +11 -7
  160. lyrics_transcriber/frontend/src/components/EditActionBar.tsx +31 -5
  161. lyrics_transcriber/frontend/src/components/EditModal.tsx +28 -10
  162. lyrics_transcriber/frontend/src/components/EditTimelineSection.tsx +123 -27
  163. lyrics_transcriber/frontend/src/components/EditWordList.tsx +112 -60
  164. lyrics_transcriber/frontend/src/components/Header.tsx +90 -76
  165. lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +53 -31
  166. lyrics_transcriber/frontend/src/components/LyricsSynchronizer/SyncControls.tsx +44 -13
  167. lyrics_transcriber/frontend/src/components/LyricsSynchronizer/TimelineCanvas.tsx +66 -50
  168. lyrics_transcriber/frontend/src/components/LyricsSynchronizer/index.tsx +124 -30
  169. lyrics_transcriber/frontend/src/components/ReferenceView.tsx +1 -1
  170. lyrics_transcriber/frontend/src/components/TimelineEditor.tsx +12 -5
  171. lyrics_transcriber/frontend/src/components/TimingOffsetModal.tsx +3 -3
  172. lyrics_transcriber/frontend/src/components/TranscriptionView.tsx +1 -1
  173. lyrics_transcriber/frontend/src/components/WordDivider.tsx +11 -7
  174. lyrics_transcriber/frontend/src/components/shared/components/Word.tsx +4 -2
  175. lyrics_transcriber/frontend/src/hooks/useManualSync.ts +103 -1
  176. lyrics_transcriber/frontend/src/theme.ts +42 -15
  177. lyrics_transcriber/frontend/tsconfig.tsbuildinfo +1 -1
  178. lyrics_transcriber/frontend/vite.config.js +5 -0
  179. lyrics_transcriber/frontend/web_assets/assets/{index-BECn1o8Q.js → index-BSMgOq4Z.js} +6959 -5782
  180. lyrics_transcriber/frontend/web_assets/assets/index-BSMgOq4Z.js.map +1 -0
  181. lyrics_transcriber/frontend/web_assets/index.html +6 -2
  182. lyrics_transcriber/frontend/web_assets/nomad-karaoke-logo.svg +5 -0
  183. lyrics_transcriber/output/generator.py +17 -3
  184. lyrics_transcriber/output/video.py +60 -95
  185. lyrics_transcriber/frontend/web_assets/assets/index-BECn1o8Q.js.map +0 -1
  186. {karaoke_gen-0.86.7.dist-info → karaoke_gen-0.96.0.dist-info}/WHEEL +0 -0
  187. {karaoke_gen-0.86.7.dist-info → karaoke_gen-0.96.0.dist-info}/entry_points.txt +0 -0
  188. {karaoke_gen-0.86.7.dist-info → karaoke_gen-0.96.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,344 @@
1
+ """
2
+ Health check routes.
3
+ """
4
+ import os
5
+ import logging
6
+ import subprocess
7
+ import platform
8
+ from fastapi import APIRouter
9
+ from typing import Dict, Any, Optional
10
+
11
+ from backend.version import VERSION
12
+ from backend.services.flacfetch_client import get_flacfetch_client
13
+ from backend.services.email_service import get_email_service
14
+ from backend.services.stripe_service import get_stripe_service
15
+ from backend.services.encoding_service import get_encoding_service
16
+
17
+ router = APIRouter()
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ def get_system_info() -> Dict[str, Any]:
22
+ """Get system information for performance analysis."""
23
+ info = {
24
+ "platform": platform.system(),
25
+ "platform_release": platform.release(),
26
+ "python_version": platform.python_version(),
27
+ }
28
+
29
+ # Get CPU info
30
+ try:
31
+ cpu_count = os.cpu_count() or 0
32
+ info["cpu_count"] = cpu_count
33
+
34
+ # Try to get more detailed CPU info on Linux
35
+ if platform.system() == "Linux":
36
+ try:
37
+ with open("/proc/cpuinfo", "r") as f:
38
+ for line in f:
39
+ if "model name" in line:
40
+ info["cpu_model"] = line.split(":")[1].strip()
41
+ break
42
+ except Exception:
43
+ pass
44
+ except Exception as e:
45
+ info["cpu_error"] = str(e)
46
+
47
+ # Get memory info
48
+ try:
49
+ if platform.system() == "Linux":
50
+ with open("/proc/meminfo", "r") as f:
51
+ for line in f:
52
+ if line.startswith("MemTotal:"):
53
+ mem_kb = int(line.split()[1])
54
+ info["memory_gb"] = round(mem_kb / (1024 * 1024), 1)
55
+ break
56
+ except Exception as e:
57
+ info["memory_error"] = str(e)
58
+
59
+ return info
60
+
61
+
62
+ def get_ffmpeg_info() -> Dict[str, Any]:
63
+ """Get FFmpeg version and configuration for debugging encoding performance."""
64
+ try:
65
+ # Get FFmpeg version
66
+ result = subprocess.run(
67
+ ["ffmpeg", "-version"],
68
+ capture_output=True,
69
+ text=True,
70
+ timeout=10
71
+ )
72
+ if result.returncode == 0:
73
+ lines = result.stdout.split("\n")
74
+ version_line = lines[0] if lines else "unknown"
75
+
76
+ # Check for hardware acceleration support
77
+ hw_accel = {}
78
+ config_result = subprocess.run(
79
+ ["ffmpeg", "-hwaccels"],
80
+ capture_output=True,
81
+ text=True,
82
+ timeout=10
83
+ )
84
+ if config_result.returncode == 0:
85
+ hw_lines = config_result.stdout.strip().split("\n")
86
+ hw_accel["available"] = [h.strip() for h in hw_lines[1:] if h.strip()]
87
+
88
+ # Check encoder availability
89
+ encoders = {}
90
+ encoder_result = subprocess.run(
91
+ ["ffmpeg", "-encoders"],
92
+ capture_output=True,
93
+ text=True,
94
+ timeout=10
95
+ )
96
+ if encoder_result.returncode == 0:
97
+ encoder_output = encoder_result.stdout
98
+ # Check for specific encoders we care about
99
+ encoders["libx264"] = "libx264" in encoder_output
100
+ encoders["h264_nvenc"] = "h264_nvenc" in encoder_output
101
+ encoders["h264_vaapi"] = "h264_vaapi" in encoder_output
102
+ encoders["hevc_nvenc"] = "hevc_nvenc" in encoder_output
103
+
104
+ return {
105
+ "available": True,
106
+ "version": version_line,
107
+ "hw_acceleration": hw_accel,
108
+ "encoders": encoders,
109
+ }
110
+ else:
111
+ return {
112
+ "available": False,
113
+ "error": result.stderr[:500] if result.stderr else "Unknown error",
114
+ }
115
+ except subprocess.TimeoutExpired:
116
+ return {"available": False, "error": "Timeout getting FFmpeg info"}
117
+ except FileNotFoundError:
118
+ return {"available": False, "error": "FFmpeg not found"}
119
+ except Exception as e:
120
+ return {"available": False, "error": str(e)}
121
+
122
+
123
+ def check_transmission_status() -> Dict[str, Any]:
124
+ """Check if Transmission daemon is available and responsive."""
125
+ try:
126
+ import transmission_rpc
127
+ host = os.environ.get("TRANSMISSION_HOST", "localhost")
128
+ port = int(os.environ.get("TRANSMISSION_PORT", "9091"))
129
+
130
+ client = transmission_rpc.Client(host=host, port=port, timeout=5)
131
+ stats = client.session_stats()
132
+
133
+ # Get detailed torrent info
134
+ torrents = client.get_torrents()
135
+ torrent_details = []
136
+ for t in torrents:
137
+ torrent_info = {
138
+ "name": t.name,
139
+ "progress": round(t.progress, 1),
140
+ "status": str(t.status),
141
+ "peers": t.peers_connected if hasattr(t, 'peers_connected') else 0,
142
+ "download_speed": round(t.rate_download / 1024, 1) if hasattr(t, 'rate_download') else 0, # KB/s
143
+ }
144
+ # Check if stalled (downloading but no progress and no peers)
145
+ if t.status.downloading and t.progress < 100 and torrent_info['peers'] == 0:
146
+ torrent_info['stalled'] = True
147
+ torrent_details.append(torrent_info)
148
+
149
+ return {
150
+ "available": True,
151
+ "host": host,
152
+ "port": port,
153
+ "download_dir": stats.download_dir if hasattr(stats, 'download_dir') else None,
154
+ "active_torrent_count": stats.active_torrent_count if hasattr(stats, 'active_torrent_count') else 0,
155
+ "torrents": torrent_details,
156
+ }
157
+ except ImportError as e:
158
+ return {
159
+ "available": False,
160
+ "error": f"transmission_rpc not installed: {e}",
161
+ }
162
+ except Exception as e:
163
+ return {
164
+ "available": False,
165
+ "host": os.environ.get("TRANSMISSION_HOST", "localhost"),
166
+ "port": int(os.environ.get("TRANSMISSION_PORT", "9091")),
167
+ "error": str(e),
168
+ }
169
+
170
+
171
+ @router.get("/health")
172
+ async def health_check() -> Dict[str, str]:
173
+ """Health check endpoint."""
174
+ return {
175
+ "status": "healthy",
176
+ "service": "karaoke-gen-backend"
177
+ }
178
+
179
+
180
+ async def check_flacfetch_service_status() -> Dict[str, Any]:
181
+ """Check if remote flacfetch service is available and healthy."""
182
+ client = get_flacfetch_client()
183
+
184
+ if not client:
185
+ return {
186
+ "configured": False,
187
+ "message": "Remote flacfetch service not configured (FLACFETCH_API_URL not set)",
188
+ }
189
+
190
+ try:
191
+ health = await client.health_check()
192
+ return {
193
+ "configured": True,
194
+ "available": True,
195
+ "status": health.get("status"),
196
+ "version": health.get("version"),
197
+ "transmission": health.get("transmission", {}),
198
+ "disk": health.get("disk", {}),
199
+ "providers": health.get("providers", {}),
200
+ }
201
+ except Exception as e:
202
+ return {
203
+ "configured": True,
204
+ "available": False,
205
+ "error": str(e),
206
+ }
207
+
208
+
209
+ async def check_encoding_worker_status() -> Dict[str, Any]:
210
+ """Check if GCE encoding worker is available and healthy."""
211
+ encoding_service = get_encoding_service()
212
+
213
+ if not encoding_service.is_configured:
214
+ return {
215
+ "configured": False,
216
+ "enabled": encoding_service.settings.use_gce_encoding,
217
+ "message": "GCE encoding worker not configured (ENCODING_WORKER_URL or API key not set)",
218
+ }
219
+
220
+ try:
221
+ health = await encoding_service.health_check()
222
+ return {
223
+ "configured": True,
224
+ "enabled": encoding_service.is_enabled,
225
+ "available": health.get("status") == "ok",
226
+ "status": health.get("status"),
227
+ "active_jobs": health.get("active_jobs", 0),
228
+ "queue_length": health.get("queue_length", 0),
229
+ "ffmpeg_version": health.get("ffmpeg_version"),
230
+ "wheel_version": health.get("wheel_version"),
231
+ }
232
+ except Exception as e:
233
+ return {
234
+ "configured": True,
235
+ "enabled": encoding_service.is_enabled,
236
+ "available": False,
237
+ "error": str(e),
238
+ }
239
+
240
+
241
+ @router.get("/health/encoding-worker")
242
+ async def encoding_worker_health() -> Dict[str, Any]:
243
+ """
244
+ Lightweight endpoint to check encoding worker status.
245
+
246
+ Returns minimal info for frontend footer display.
247
+ No authentication required.
248
+ """
249
+ status = await check_encoding_worker_status()
250
+
251
+ # Return simplified response for frontend
252
+ if not status.get("configured"):
253
+ return {
254
+ "available": False,
255
+ "status": "not_configured",
256
+ }
257
+
258
+ if not status.get("available"):
259
+ return {
260
+ "available": False,
261
+ "status": "offline",
262
+ "error": status.get("error"),
263
+ }
264
+
265
+ return {
266
+ "available": True,
267
+ "status": "ok",
268
+ "version": status.get("wheel_version"),
269
+ "active_jobs": status.get("active_jobs", 0),
270
+ "queue_length": status.get("queue_length", 0),
271
+ }
272
+
273
+
274
+ @router.get("/health/detailed")
275
+ async def detailed_health_check() -> Dict[str, Any]:
276
+ """
277
+ Detailed health check including dependencies.
278
+
279
+ Use this to debug issues with Transmission, flacfetch service, etc.
280
+ """
281
+ transmission_status = check_transmission_status()
282
+ flacfetch_status = await check_flacfetch_service_status()
283
+ encoding_status = await check_encoding_worker_status()
284
+
285
+ # Check email service
286
+ email_service = get_email_service()
287
+ email_status = {
288
+ "configured": email_service.is_configured(),
289
+ "provider": type(email_service.provider).__name__,
290
+ }
291
+
292
+ # Check Stripe service
293
+ stripe_service = get_stripe_service()
294
+ stripe_status = {
295
+ "configured": stripe_service.is_configured(),
296
+ }
297
+
298
+ # Log for debugging
299
+ if not transmission_status.get("available"):
300
+ logger.warning(f"Local Transmission not available: {transmission_status.get('error')}")
301
+ else:
302
+ logger.info(f"Local Transmission available at {transmission_status.get('host')}:{transmission_status.get('port')}")
303
+
304
+ if flacfetch_status.get("configured") and not flacfetch_status.get("available"):
305
+ logger.warning(f"Remote flacfetch service not available: {flacfetch_status.get('error')}")
306
+ elif flacfetch_status.get("available"):
307
+ logger.info(f"Remote flacfetch service healthy: {flacfetch_status.get('status')}")
308
+
309
+ if not email_status["configured"]:
310
+ logger.warning("Email service not configured - magic links will not work")
311
+
312
+ if not stripe_status["configured"]:
313
+ logger.warning("Stripe service not configured - payments will not work")
314
+
315
+ # Get system and FFmpeg info for performance analysis
316
+ system_info = get_system_info()
317
+ ffmpeg_info = get_ffmpeg_info()
318
+
319
+ return {
320
+ "status": "healthy",
321
+ "service": "karaoke-gen-backend",
322
+ "version": VERSION,
323
+ "system": system_info,
324
+ "ffmpeg": ffmpeg_info,
325
+ "dependencies": {
326
+ "transmission_local": transmission_status,
327
+ "flacfetch_remote": flacfetch_status,
328
+ "encoding_worker": encoding_status,
329
+ },
330
+ "services": {
331
+ "email": email_status,
332
+ "stripe": stripe_status,
333
+ }
334
+ }
335
+
336
+
337
+ @router.get("/readiness")
338
+ async def readiness_check() -> Dict[str, str]:
339
+ """Readiness check endpoint for Cloud Run."""
340
+ return {
341
+ "status": "ready",
342
+ "service": "karaoke-gen-backend"
343
+ }
344
+