karaoke-gen 0.103.1__py3-none-any.whl → 0.107.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.
- backend/Dockerfile.base +1 -0
- backend/api/routes/admin.py +226 -3
- backend/api/routes/push.py +238 -0
- backend/api/routes/users.py +14 -3
- backend/config.py +12 -1
- backend/main.py +2 -1
- backend/models/job.py +4 -0
- backend/models/user.py +20 -2
- backend/services/encoding_interface.py +4 -0
- backend/services/gce_encoding/main.py +22 -8
- backend/services/job_manager.py +68 -11
- backend/services/job_notification_service.py +4 -21
- backend/services/push_notification_service.py +409 -0
- backend/services/stripe_service.py +2 -2
- backend/tests/conftest.py +2 -1
- backend/tests/test_admin_delete_outputs.py +352 -0
- backend/tests/test_gce_encoding_worker.py +229 -0
- backend/tests/test_impersonation.py +18 -3
- backend/tests/test_job_notification_service.py +24 -58
- backend/tests/test_push_notification_service.py +460 -0
- backend/tests/test_push_routes.py +357 -0
- backend/tests/test_stripe_service.py +205 -0
- backend/tests/test_video_worker_orchestrator.py +189 -0
- backend/workers/video_worker_orchestrator.py +23 -0
- karaoke_gen/instrumental_review/server.py +145 -35
- karaoke_gen/nextjs_frontend/__init__.py +98 -0
- karaoke_gen/nextjs_frontend/out/404/index.html +1 -0
- karaoke_gen/nextjs_frontend/out/404.html +1 -0
- karaoke_gen/nextjs_frontend/out/__next.__PAGE__.txt +9 -0
- karaoke_gen/nextjs_frontend/out/__next._full.txt +22 -0
- karaoke_gen/nextjs_frontend/out/__next._head.txt +8 -0
- karaoke_gen/nextjs_frontend/out/__next._index.txt +9 -0
- karaoke_gen/nextjs_frontend/out/__next._tree.txt +2 -0
- karaoke_gen/nextjs_frontend/out/_next/static/chunks/01a7f8fe40f1ff47.js +1 -0
- karaoke_gen/nextjs_frontend/out/_next/static/chunks/112f346e31f991df.js +4 -0
- karaoke_gen/nextjs_frontend/out/_next/static/chunks/16d1a4dd9d8a873a.js +3 -0
- karaoke_gen/nextjs_frontend/out/_next/static/chunks/1ab85c362b8b0e86.js +9 -0
- karaoke_gen/nextjs_frontend/out/_next/static/chunks/247eb132b7f7b574.js +1 -0
- karaoke_gen/nextjs_frontend/out/_next/static/chunks/2b80d15cc95e4818.js +1 -0
- karaoke_gen/nextjs_frontend/out/_next/static/chunks/32c7eba5cd46c1bc.js +7 -0
- karaoke_gen/nextjs_frontend/out/_next/static/chunks/483f26794eae53d0.js +1 -0
- karaoke_gen/nextjs_frontend/out/_next/static/chunks/550c3b02e85f196a.js +1 -0
- karaoke_gen/nextjs_frontend/out/_next/static/chunks/55c5ade44387bef8.js +1 -0
- karaoke_gen/nextjs_frontend/out/_next/static/chunks/5628d92b5893add2.css +1 -0
- karaoke_gen/nextjs_frontend/out/_next/static/chunks/56ebf7665e4341c8.js +7 -0
- karaoke_gen/nextjs_frontend/out/_next/static/chunks/5997132b61dec430.js +1 -0
- karaoke_gen/nextjs_frontend/out/_next/static/chunks/5ea55255bce3eb9e.js +5 -0
- karaoke_gen/nextjs_frontend/out/_next/static/chunks/5eda89a57490b3cd.js +1 -0
- karaoke_gen/nextjs_frontend/out/_next/static/chunks/692f5d9e0d700c76.js +3 -0
- karaoke_gen/nextjs_frontend/out/_next/static/chunks/71d7a05b14f9f0f4.js +1 -0
- karaoke_gen/nextjs_frontend/out/_next/static/chunks/81ac355749ef3302.js +1 -0
- karaoke_gen/nextjs_frontend/out/_next/static/chunks/95f7e5934dbb0e5d.js +1 -0
- karaoke_gen/nextjs_frontend/out/_next/static/chunks/9bce8f19eaa46940.js +1 -0
- karaoke_gen/nextjs_frontend/out/_next/static/chunks/a6dad97d9634a72d.js +1 -0
- karaoke_gen/nextjs_frontend/out/_next/static/chunks/a9ed54eed3e14c92.js +2 -0
- karaoke_gen/nextjs_frontend/out/_next/static/chunks/b35cd41238ecfb17.js +1 -0
- karaoke_gen/nextjs_frontend/out/_next/static/chunks/b5bc3c3d5ebd49eb.js +1 -0
- karaoke_gen/nextjs_frontend/out/_next/static/chunks/b5c078c08db5ae32.js +5 -0
- karaoke_gen/nextjs_frontend/out/_next/static/chunks/be9c44a178104187.js +1 -0
- karaoke_gen/nextjs_frontend/out/_next/static/chunks/c4c840e18cb4861c.js +1 -0
- karaoke_gen/nextjs_frontend/out/_next/static/chunks/c645af7d6b65f73e.js +1 -0
- karaoke_gen/nextjs_frontend/out/_next/static/chunks/d2c5e2575df784d4.js +1 -0
- karaoke_gen/nextjs_frontend/out/_next/static/chunks/d30af02b96d81462.js +1 -0
- karaoke_gen/nextjs_frontend/out/_next/static/chunks/d9bdf64f4ec1e9b7.js +7 -0
- karaoke_gen/nextjs_frontend/out/_next/static/chunks/dcde6ed684dacd0e.js +5 -0
- karaoke_gen/nextjs_frontend/out/_next/static/chunks/e422cbe931246000.js +1 -0
- karaoke_gen/nextjs_frontend/out/_next/static/chunks/e483af34fc792d38.js +1 -0
- karaoke_gen/nextjs_frontend/out/_next/static/chunks/e57422aad6b897da.js +1 -0
- karaoke_gen/nextjs_frontend/out/_next/static/chunks/ef02697fb404726a.js +1 -0
- karaoke_gen/nextjs_frontend/out/_next/static/chunks/ff1a16fafef87110.js +1 -0
- karaoke_gen/nextjs_frontend/out/_next/static/chunks/turbopack-2d9ca3017a9deedf.js +3 -0
- karaoke_gen/nextjs_frontend/out/_next/static/zpw_-rjFIDV5tlPPtnvRI/_buildManifest.js +11 -0
- karaoke_gen/nextjs_frontend/out/_next/static/zpw_-rjFIDV5tlPPtnvRI/_clientMiddlewareManifest.json +1 -0
- karaoke_gen/nextjs_frontend/out/_next/static/zpw_-rjFIDV5tlPPtnvRI/_ssgManifest.js +1 -0
- karaoke_gen/nextjs_frontend/out/_not-found/__next._full.txt +18 -0
- karaoke_gen/nextjs_frontend/out/_not-found/__next._head.txt +8 -0
- karaoke_gen/nextjs_frontend/out/_not-found/__next._index.txt +9 -0
- karaoke_gen/nextjs_frontend/out/_not-found/__next._not-found.__PAGE__.txt +5 -0
- karaoke_gen/nextjs_frontend/out/_not-found/__next._not-found.txt +4 -0
- karaoke_gen/nextjs_frontend/out/_not-found/__next._tree.txt +2 -0
- karaoke_gen/nextjs_frontend/out/_not-found/index.html +1 -0
- karaoke_gen/nextjs_frontend/out/_not-found/index.txt +18 -0
- karaoke_gen/nextjs_frontend/out/admin/__next._full.txt +25 -0
- karaoke_gen/nextjs_frontend/out/admin/__next._head.txt +8 -0
- karaoke_gen/nextjs_frontend/out/admin/__next._index.txt +9 -0
- karaoke_gen/nextjs_frontend/out/admin/__next._tree.txt +2 -0
- karaoke_gen/nextjs_frontend/out/admin/__next.admin.__PAGE__.txt +9 -0
- karaoke_gen/nextjs_frontend/out/admin/__next.admin.txt +7 -0
- karaoke_gen/nextjs_frontend/out/admin/beta/__next._full.txt +25 -0
- karaoke_gen/nextjs_frontend/out/admin/beta/__next._head.txt +8 -0
- karaoke_gen/nextjs_frontend/out/admin/beta/__next._index.txt +9 -0
- karaoke_gen/nextjs_frontend/out/admin/beta/__next._tree.txt +2 -0
- karaoke_gen/nextjs_frontend/out/admin/beta/__next.admin.beta.__PAGE__.txt +9 -0
- karaoke_gen/nextjs_frontend/out/admin/beta/__next.admin.beta.txt +4 -0
- karaoke_gen/nextjs_frontend/out/admin/beta/__next.admin.txt +7 -0
- karaoke_gen/nextjs_frontend/out/admin/beta/index.html +1 -0
- karaoke_gen/nextjs_frontend/out/admin/beta/index.txt +25 -0
- karaoke_gen/nextjs_frontend/out/admin/index.html +1 -0
- karaoke_gen/nextjs_frontend/out/admin/index.txt +25 -0
- karaoke_gen/nextjs_frontend/out/admin/jobs/__next._full.txt +25 -0
- karaoke_gen/nextjs_frontend/out/admin/jobs/__next._head.txt +8 -0
- karaoke_gen/nextjs_frontend/out/admin/jobs/__next._index.txt +9 -0
- karaoke_gen/nextjs_frontend/out/admin/jobs/__next._tree.txt +2 -0
- karaoke_gen/nextjs_frontend/out/admin/jobs/__next.admin.jobs.__PAGE__.txt +9 -0
- karaoke_gen/nextjs_frontend/out/admin/jobs/__next.admin.jobs.txt +4 -0
- karaoke_gen/nextjs_frontend/out/admin/jobs/__next.admin.txt +7 -0
- karaoke_gen/nextjs_frontend/out/admin/jobs/index.html +1 -0
- karaoke_gen/nextjs_frontend/out/admin/jobs/index.txt +25 -0
- karaoke_gen/nextjs_frontend/out/admin/rate-limits/__next._full.txt +25 -0
- karaoke_gen/nextjs_frontend/out/admin/rate-limits/__next._head.txt +8 -0
- karaoke_gen/nextjs_frontend/out/admin/rate-limits/__next._index.txt +9 -0
- karaoke_gen/nextjs_frontend/out/admin/rate-limits/__next._tree.txt +2 -0
- karaoke_gen/nextjs_frontend/out/admin/rate-limits/__next.admin.rate-limits.__PAGE__.txt +9 -0
- karaoke_gen/nextjs_frontend/out/admin/rate-limits/__next.admin.rate-limits.txt +4 -0
- karaoke_gen/nextjs_frontend/out/admin/rate-limits/__next.admin.txt +7 -0
- karaoke_gen/nextjs_frontend/out/admin/rate-limits/index.html +1 -0
- karaoke_gen/nextjs_frontend/out/admin/rate-limits/index.txt +25 -0
- karaoke_gen/nextjs_frontend/out/admin/searches/__next._full.txt +25 -0
- karaoke_gen/nextjs_frontend/out/admin/searches/__next._head.txt +8 -0
- karaoke_gen/nextjs_frontend/out/admin/searches/__next._index.txt +9 -0
- karaoke_gen/nextjs_frontend/out/admin/searches/__next._tree.txt +2 -0
- karaoke_gen/nextjs_frontend/out/admin/searches/__next.admin.searches.__PAGE__.txt +9 -0
- karaoke_gen/nextjs_frontend/out/admin/searches/__next.admin.searches.txt +4 -0
- karaoke_gen/nextjs_frontend/out/admin/searches/__next.admin.txt +7 -0
- karaoke_gen/nextjs_frontend/out/admin/searches/index.html +1 -0
- karaoke_gen/nextjs_frontend/out/admin/searches/index.txt +25 -0
- karaoke_gen/nextjs_frontend/out/admin/users/__next._full.txt +25 -0
- karaoke_gen/nextjs_frontend/out/admin/users/__next._head.txt +8 -0
- karaoke_gen/nextjs_frontend/out/admin/users/__next._index.txt +9 -0
- karaoke_gen/nextjs_frontend/out/admin/users/__next._tree.txt +2 -0
- karaoke_gen/nextjs_frontend/out/admin/users/__next.admin.txt +7 -0
- karaoke_gen/nextjs_frontend/out/admin/users/__next.admin.users.__PAGE__.txt +9 -0
- karaoke_gen/nextjs_frontend/out/admin/users/__next.admin.users.txt +4 -0
- karaoke_gen/nextjs_frontend/out/admin/users/detail/__next._full.txt +25 -0
- karaoke_gen/nextjs_frontend/out/admin/users/detail/__next._head.txt +8 -0
- karaoke_gen/nextjs_frontend/out/admin/users/detail/__next._index.txt +9 -0
- karaoke_gen/nextjs_frontend/out/admin/users/detail/__next._tree.txt +2 -0
- karaoke_gen/nextjs_frontend/out/admin/users/detail/__next.admin.txt +7 -0
- karaoke_gen/nextjs_frontend/out/admin/users/detail/__next.admin.users.detail.__PAGE__.txt +9 -0
- karaoke_gen/nextjs_frontend/out/admin/users/detail/__next.admin.users.detail.txt +4 -0
- karaoke_gen/nextjs_frontend/out/admin/users/detail/__next.admin.users.txt +4 -0
- karaoke_gen/nextjs_frontend/out/admin/users/detail/index.html +1 -0
- karaoke_gen/nextjs_frontend/out/admin/users/detail/index.txt +25 -0
- karaoke_gen/nextjs_frontend/out/admin/users/index.html +1 -0
- karaoke_gen/nextjs_frontend/out/admin/users/index.txt +25 -0
- karaoke_gen/nextjs_frontend/out/app/__next._full.txt +22 -0
- karaoke_gen/nextjs_frontend/out/app/__next._head.txt +8 -0
- karaoke_gen/nextjs_frontend/out/app/__next._index.txt +9 -0
- karaoke_gen/nextjs_frontend/out/app/__next._tree.txt +2 -0
- karaoke_gen/nextjs_frontend/out/app/__next.app.__PAGE__.txt +9 -0
- karaoke_gen/nextjs_frontend/out/app/__next.app.txt +4 -0
- karaoke_gen/nextjs_frontend/out/app/index.html +1 -0
- karaoke_gen/nextjs_frontend/out/app/index.txt +22 -0
- karaoke_gen/nextjs_frontend/out/app/jobs/__next._full.txt +19 -0
- karaoke_gen/nextjs_frontend/out/app/jobs/__next._head.txt +8 -0
- karaoke_gen/nextjs_frontend/out/app/jobs/__next._index.txt +9 -0
- karaoke_gen/nextjs_frontend/out/app/jobs/__next._tree.txt +2 -0
- karaoke_gen/nextjs_frontend/out/app/jobs/__next.app.jobs.$oc$slug.__PAGE__.txt +6 -0
- karaoke_gen/nextjs_frontend/out/app/jobs/__next.app.jobs.$oc$slug.txt +4 -0
- karaoke_gen/nextjs_frontend/out/app/jobs/__next.app.jobs.txt +4 -0
- karaoke_gen/nextjs_frontend/out/app/jobs/__next.app.txt +4 -0
- karaoke_gen/nextjs_frontend/out/app/jobs/index.html +1 -0
- karaoke_gen/nextjs_frontend/out/app/jobs/index.txt +19 -0
- karaoke_gen/nextjs_frontend/out/app/jobs/local/instrumental/__next._full.txt +19 -0
- karaoke_gen/nextjs_frontend/out/app/jobs/local/instrumental/__next._head.txt +8 -0
- karaoke_gen/nextjs_frontend/out/app/jobs/local/instrumental/__next._index.txt +9 -0
- karaoke_gen/nextjs_frontend/out/app/jobs/local/instrumental/__next._tree.txt +2 -0
- karaoke_gen/nextjs_frontend/out/app/jobs/local/instrumental/__next.app.jobs.$oc$slug.__PAGE__.txt +6 -0
- karaoke_gen/nextjs_frontend/out/app/jobs/local/instrumental/__next.app.jobs.$oc$slug.txt +4 -0
- karaoke_gen/nextjs_frontend/out/app/jobs/local/instrumental/__next.app.jobs.txt +4 -0
- karaoke_gen/nextjs_frontend/out/app/jobs/local/instrumental/__next.app.txt +4 -0
- karaoke_gen/nextjs_frontend/out/app/jobs/local/instrumental/index.html +1 -0
- karaoke_gen/nextjs_frontend/out/app/jobs/local/instrumental/index.txt +19 -0
- karaoke_gen/nextjs_frontend/out/app/jobs/local/review/__next._full.txt +19 -0
- karaoke_gen/nextjs_frontend/out/app/jobs/local/review/__next._head.txt +8 -0
- karaoke_gen/nextjs_frontend/out/app/jobs/local/review/__next._index.txt +9 -0
- karaoke_gen/nextjs_frontend/out/app/jobs/local/review/__next._tree.txt +2 -0
- karaoke_gen/nextjs_frontend/out/app/jobs/local/review/__next.app.jobs.$oc$slug.__PAGE__.txt +6 -0
- karaoke_gen/nextjs_frontend/out/app/jobs/local/review/__next.app.jobs.$oc$slug.txt +4 -0
- karaoke_gen/nextjs_frontend/out/app/jobs/local/review/__next.app.jobs.txt +4 -0
- karaoke_gen/nextjs_frontend/out/app/jobs/local/review/__next.app.txt +4 -0
- karaoke_gen/nextjs_frontend/out/app/jobs/local/review/index.html +1 -0
- karaoke_gen/nextjs_frontend/out/app/jobs/local/review/index.txt +19 -0
- karaoke_gen/nextjs_frontend/out/auth/verify/__next._full.txt +22 -0
- karaoke_gen/nextjs_frontend/out/auth/verify/__next._head.txt +8 -0
- karaoke_gen/nextjs_frontend/out/auth/verify/__next._index.txt +9 -0
- karaoke_gen/nextjs_frontend/out/auth/verify/__next._tree.txt +2 -0
- karaoke_gen/nextjs_frontend/out/auth/verify/__next.auth.txt +4 -0
- karaoke_gen/nextjs_frontend/out/auth/verify/__next.auth.verify.__PAGE__.txt +9 -0
- karaoke_gen/nextjs_frontend/out/auth/verify/__next.auth.verify.txt +4 -0
- karaoke_gen/nextjs_frontend/out/auth/verify/index.html +1 -0
- karaoke_gen/nextjs_frontend/out/auth/verify/index.txt +22 -0
- karaoke_gen/nextjs_frontend/out/index.html +1 -0
- karaoke_gen/nextjs_frontend/out/index.txt +22 -0
- karaoke_gen/nextjs_frontend/out/manifest.webmanifest +31 -0
- karaoke_gen/nextjs_frontend/out/order/success/__next._full.txt +22 -0
- karaoke_gen/nextjs_frontend/out/order/success/__next._head.txt +8 -0
- karaoke_gen/nextjs_frontend/out/order/success/__next._index.txt +9 -0
- karaoke_gen/nextjs_frontend/out/order/success/__next._tree.txt +2 -0
- karaoke_gen/nextjs_frontend/out/order/success/__next.order.success.__PAGE__.txt +9 -0
- karaoke_gen/nextjs_frontend/out/order/success/__next.order.success.txt +4 -0
- karaoke_gen/nextjs_frontend/out/order/success/__next.order.txt +4 -0
- karaoke_gen/nextjs_frontend/out/order/success/index.html +1 -0
- karaoke_gen/nextjs_frontend/out/order/success/index.txt +22 -0
- karaoke_gen/nextjs_frontend/out/payment/success/__next._full.txt +22 -0
- karaoke_gen/nextjs_frontend/out/payment/success/__next._head.txt +8 -0
- karaoke_gen/nextjs_frontend/out/payment/success/__next._index.txt +9 -0
- karaoke_gen/nextjs_frontend/out/payment/success/__next._tree.txt +2 -0
- karaoke_gen/nextjs_frontend/out/payment/success/__next.payment.success.__PAGE__.txt +9 -0
- karaoke_gen/nextjs_frontend/out/payment/success/__next.payment.success.txt +4 -0
- karaoke_gen/nextjs_frontend/out/payment/success/__next.payment.txt +4 -0
- karaoke_gen/nextjs_frontend/out/payment/success/index.html +1 -0
- karaoke_gen/nextjs_frontend/out/payment/success/index.txt +22 -0
- karaoke_gen/nextjs_frontend/out/screenshots/email-action_reminder.png +0 -0
- karaoke_gen/nextjs_frontend/out/screenshots/email-beta_welcome.png +0 -0
- karaoke_gen/nextjs_frontend/out/screenshots/email-job_completion.png +0 -0
- karaoke_gen/nextjs_frontend/out/screenshots/example-output.avif +0 -0
- karaoke_gen/nextjs_frontend/out/screenshots/homepage-full.png +0 -0
- karaoke_gen/nextjs_frontend/out/screenshots/homepage-hero.png +0 -0
- karaoke_gen/nextjs_frontend/out/screenshots/instrumental-review.avif +0 -0
- karaoke_gen/nextjs_frontend/out/screenshots/instrumental-review.png +0 -0
- karaoke_gen/nextjs_frontend/out/screenshots/job-dashboard.avif +0 -0
- karaoke_gen/nextjs_frontend/out/screenshots/lyrics-review.avif +0 -0
- karaoke_gen/nextjs_frontend/out/screenshots/lyrics-review.png +0 -0
- karaoke_gen/nextjs_frontend/out/sw.js +183 -0
- karaoke_gen/utils/cli_args.py +3 -3
- karaoke_gen/utils/gen_cli.py +4 -0
- karaoke_gen/utils/remote_cli.py +8 -40
- {karaoke_gen-0.103.1.dist-info → karaoke_gen-0.107.0.dist-info}/METADATA +2 -1
- {karaoke_gen-0.103.1.dist-info → karaoke_gen-0.107.0.dist-info}/RECORD +244 -131
- {karaoke_gen-0.103.1.dist-info → karaoke_gen-0.107.0.dist-info}/WHEEL +1 -1
- lyrics_transcriber/correction/agentic/agent.py +83 -60
- lyrics_transcriber/correction/anchor_sequence.py +48 -3
- lyrics_transcriber/correction/corrector.py +92 -58
- lyrics_transcriber/review/server.py +165 -33
- lyrics_transcriber/utils/tracing.py +214 -0
- karaoke_gen/instrumental_review/static/index.html +0 -1695
- lyrics_transcriber/frontend/.gitignore +0 -24
- lyrics_transcriber/frontend/.yarn/releases/yarn-4.7.0.cjs +0 -935
- lyrics_transcriber/frontend/.yarnrc.yml +0 -3
- lyrics_transcriber/frontend/README.md +0 -50
- lyrics_transcriber/frontend/REPLACE_ALL_FUNCTIONALITY.md +0 -210
- lyrics_transcriber/frontend/__init__.py +0 -25
- lyrics_transcriber/frontend/e2e/agentic-corrections.spec.ts +0 -207
- lyrics_transcriber/frontend/e2e/fixtures/agentic-correction-data.json +0 -226
- lyrics_transcriber/frontend/eslint.config.js +0 -28
- lyrics_transcriber/frontend/index.html +0 -22
- lyrics_transcriber/frontend/package-lock.json +0 -4553
- lyrics_transcriber/frontend/package.json +0 -48
- lyrics_transcriber/frontend/playwright.config.ts +0 -69
- lyrics_transcriber/frontend/public/android-chrome-192x192.png +0 -0
- lyrics_transcriber/frontend/public/android-chrome-512x512.png +0 -0
- lyrics_transcriber/frontend/src/App.tsx +0 -243
- lyrics_transcriber/frontend/src/api.ts +0 -262
- lyrics_transcriber/frontend/src/components/AIFeedbackModal.tsx +0 -111
- lyrics_transcriber/frontend/src/components/AddLyricsModal.tsx +0 -114
- lyrics_transcriber/frontend/src/components/AgenticCorrectionMetrics.tsx +0 -204
- lyrics_transcriber/frontend/src/components/AppHeader.tsx +0 -65
- lyrics_transcriber/frontend/src/components/AudioPlayer.tsx +0 -180
- lyrics_transcriber/frontend/src/components/CorrectedWordWithActions.tsx +0 -175
- lyrics_transcriber/frontend/src/components/CorrectionAnnotationModal.tsx +0 -359
- lyrics_transcriber/frontend/src/components/CorrectionDetailCard.tsx +0 -281
- lyrics_transcriber/frontend/src/components/CorrectionMetrics.tsx +0 -162
- lyrics_transcriber/frontend/src/components/DurationTimelineView.tsx +0 -257
- lyrics_transcriber/frontend/src/components/EditActionBar.tsx +0 -94
- lyrics_transcriber/frontend/src/components/EditModal.tsx +0 -720
- lyrics_transcriber/frontend/src/components/EditTimelineSection.tsx +0 -592
- lyrics_transcriber/frontend/src/components/EditWordList.tsx +0 -431
- lyrics_transcriber/frontend/src/components/FileUpload.tsx +0 -77
- lyrics_transcriber/frontend/src/components/FindReplaceModal.tsx +0 -467
- lyrics_transcriber/frontend/src/components/Header.tsx +0 -520
- lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +0 -1526
- lyrics_transcriber/frontend/src/components/LyricsSynchronizer/SyncControls.tsx +0 -216
- lyrics_transcriber/frontend/src/components/LyricsSynchronizer/TimelineCanvas.tsx +0 -721
- lyrics_transcriber/frontend/src/components/LyricsSynchronizer/UpcomingWordsBar.tsx +0 -80
- lyrics_transcriber/frontend/src/components/LyricsSynchronizer/index.tsx +0 -999
- lyrics_transcriber/frontend/src/components/MetricsDashboard.tsx +0 -51
- lyrics_transcriber/frontend/src/components/ModeSelectionModal.tsx +0 -127
- lyrics_transcriber/frontend/src/components/ModeSelector.tsx +0 -67
- lyrics_transcriber/frontend/src/components/ModelSelector.tsx +0 -23
- lyrics_transcriber/frontend/src/components/PreviewVideoSection.tsx +0 -177
- lyrics_transcriber/frontend/src/components/ReferenceView.tsx +0 -268
- lyrics_transcriber/frontend/src/components/ReplaceAllLyricsModal.tsx +0 -336
- lyrics_transcriber/frontend/src/components/ReviewChangesModal.tsx +0 -354
- lyrics_transcriber/frontend/src/components/SegmentDetailsModal.tsx +0 -64
- lyrics_transcriber/frontend/src/components/TimelineEditor.tsx +0 -383
- lyrics_transcriber/frontend/src/components/TimingOffsetModal.tsx +0 -131
- lyrics_transcriber/frontend/src/components/TranscriptionView.tsx +0 -266
- lyrics_transcriber/frontend/src/components/WordDivider.tsx +0 -191
- lyrics_transcriber/frontend/src/components/shared/components/HighlightedText.tsx +0 -466
- lyrics_transcriber/frontend/src/components/shared/components/SourceSelector.tsx +0 -56
- lyrics_transcriber/frontend/src/components/shared/components/Word.tsx +0 -89
- lyrics_transcriber/frontend/src/components/shared/constants.ts +0 -30
- lyrics_transcriber/frontend/src/components/shared/hooks/useWordClick.ts +0 -180
- lyrics_transcriber/frontend/src/components/shared/styles.ts +0 -13
- lyrics_transcriber/frontend/src/components/shared/types.js +0 -2
- lyrics_transcriber/frontend/src/components/shared/types.ts +0 -135
- lyrics_transcriber/frontend/src/components/shared/utils/keyboardHandlers.ts +0 -177
- lyrics_transcriber/frontend/src/components/shared/utils/localStorage.ts +0 -78
- lyrics_transcriber/frontend/src/components/shared/utils/referenceLineCalculator.ts +0 -75
- lyrics_transcriber/frontend/src/components/shared/utils/segmentOperations.ts +0 -360
- lyrics_transcriber/frontend/src/components/shared/utils/timingUtils.ts +0 -110
- lyrics_transcriber/frontend/src/components/shared/utils/wordUtils.ts +0 -22
- lyrics_transcriber/frontend/src/hooks/useManualSync.ts +0 -537
- lyrics_transcriber/frontend/src/main.tsx +0 -11
- lyrics_transcriber/frontend/src/theme.ts +0 -406
- lyrics_transcriber/frontend/src/types/global.d.ts +0 -9
- lyrics_transcriber/frontend/src/types.js +0 -2
- lyrics_transcriber/frontend/src/types.ts +0 -199
- lyrics_transcriber/frontend/src/validation.ts +0 -132
- lyrics_transcriber/frontend/src/vite-env.d.ts +0 -1
- lyrics_transcriber/frontend/tsconfig.app.json +0 -26
- lyrics_transcriber/frontend/tsconfig.json +0 -25
- lyrics_transcriber/frontend/tsconfig.node.json +0 -23
- lyrics_transcriber/frontend/tsconfig.tsbuildinfo +0 -1
- lyrics_transcriber/frontend/update_version.js +0 -11
- lyrics_transcriber/frontend/vite.config.d.ts +0 -2
- lyrics_transcriber/frontend/vite.config.js +0 -15
- lyrics_transcriber/frontend/vite.config.ts +0 -16
- lyrics_transcriber/frontend/web_assets/android-chrome-192x192.png +0 -0
- lyrics_transcriber/frontend/web_assets/android-chrome-512x512.png +0 -0
- lyrics_transcriber/frontend/web_assets/apple-touch-icon.png +0 -0
- lyrics_transcriber/frontend/web_assets/assets/index-BSMgOq4Z.js +0 -44465
- lyrics_transcriber/frontend/web_assets/assets/index-BSMgOq4Z.js.map +0 -1
- lyrics_transcriber/frontend/web_assets/favicon-16x16.png +0 -0
- lyrics_transcriber/frontend/web_assets/favicon-32x32.png +0 -0
- lyrics_transcriber/frontend/web_assets/favicon.ico +0 -0
- lyrics_transcriber/frontend/web_assets/index.html +0 -22
- lyrics_transcriber/frontend/web_assets/nomad-karaoke-logo.png +0 -0
- lyrics_transcriber/frontend/web_assets/nomad-karaoke-logo.svg +0 -5
- lyrics_transcriber/frontend/yarn.lock +0 -3711
- {lyrics_transcriber/frontend/public → karaoke_gen/nextjs_frontend/out}/apple-touch-icon.png +0 -0
- {lyrics_transcriber/frontend/public → karaoke_gen/nextjs_frontend/out}/favicon-16x16.png +0 -0
- {lyrics_transcriber/frontend/public → karaoke_gen/nextjs_frontend/out}/favicon-32x32.png +0 -0
- {lyrics_transcriber/frontend/public → karaoke_gen/nextjs_frontend/out}/favicon.ico +0 -0
- {lyrics_transcriber/frontend/public → karaoke_gen/nextjs_frontend/out}/nomad-karaoke-logo.svg +0 -0
- /lyrics_transcriber/frontend/public/nomad-karaoke-logo.png → /karaoke_gen/nextjs_frontend/out/nomad-logo.png +0 -0
- {karaoke_gen-0.103.1.dist-info → karaoke_gen-0.107.0.dist-info}/entry_points.txt +0 -0
- {karaoke_gen-0.103.1.dist-info → karaoke_gen-0.107.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -84,6 +84,7 @@ class ReviewServer:
|
|
|
84
84
|
self._configure_cors()
|
|
85
85
|
self._register_routes()
|
|
86
86
|
self._mount_frontend()
|
|
87
|
+
self._register_spa_routes()
|
|
87
88
|
# Initialize optional SQLite store for sessions/feedback (legacy)
|
|
88
89
|
try:
|
|
89
90
|
default_db = os.path.join(self.output_config.cache_dir, "agentic_feedback.sqlite3")
|
|
@@ -135,18 +136,111 @@ class ReviewServer:
|
|
|
135
136
|
return JSONResponse(status_code=500, content={"error": "InternalServerError", "message": str(exc), "details": {}})
|
|
136
137
|
|
|
137
138
|
def _mount_frontend(self) -> None:
|
|
138
|
-
"""Mount the frontend static files."""
|
|
139
|
-
|
|
140
|
-
from lyrics_transcriber.frontend import get_frontend_assets_dir
|
|
141
|
-
frontend_dir = get_frontend_assets_dir()
|
|
139
|
+
"""Mount the unified Next.js frontend static files."""
|
|
140
|
+
from karaoke_gen.nextjs_frontend import get_nextjs_assets_dir, is_nextjs_frontend_available
|
|
142
141
|
|
|
143
|
-
if not
|
|
144
|
-
raise FileNotFoundError(
|
|
142
|
+
if not is_nextjs_frontend_available():
|
|
143
|
+
raise FileNotFoundError(
|
|
144
|
+
"Next.js frontend assets not found. Please ensure the frontend is built "
|
|
145
|
+
"and copied to karaoke_gen/nextjs_frontend/out/"
|
|
146
|
+
)
|
|
145
147
|
|
|
146
|
-
|
|
148
|
+
frontend_dir = str(get_nextjs_assets_dir())
|
|
149
|
+
self.logger.info(f"Using Next.js frontend from {frontend_dir}")
|
|
150
|
+
self._frontend_dir = frontend_dir # Store for use in route handlers
|
|
151
|
+
|
|
152
|
+
# Mount Next.js static assets directory
|
|
153
|
+
nextjs_static = os.path.join(frontend_dir, "_next")
|
|
154
|
+
if os.path.exists(nextjs_static):
|
|
155
|
+
self.app.mount("/_next", StaticFiles(directory=nextjs_static), name="nextjs_static")
|
|
156
|
+
|
|
157
|
+
# Mount the entire frontend directory for static files, but use html=False
|
|
158
|
+
# so it doesn't serve index.html automatically for directories
|
|
159
|
+
self.app.mount("/static", StaticFiles(directory=frontend_dir), name="frontend_static")
|
|
160
|
+
|
|
161
|
+
def _register_spa_routes(self) -> None:
|
|
162
|
+
"""Register SPA fallback routes for Next.js client-side routing."""
|
|
163
|
+
frontend_dir = self._frontend_dir
|
|
164
|
+
|
|
165
|
+
# Root route - serve index.html
|
|
166
|
+
@self.app.get("/")
|
|
167
|
+
async def serve_root():
|
|
168
|
+
index_html = os.path.join(frontend_dir, "index.html")
|
|
169
|
+
if os.path.exists(index_html):
|
|
170
|
+
return FileResponse(index_html, media_type="text/html")
|
|
171
|
+
raise HTTPException(status_code=404, detail="Frontend not found")
|
|
172
|
+
|
|
173
|
+
# Local review route - serve pre-rendered HTML for local mode
|
|
174
|
+
@self.app.get("/app/jobs/local/review")
|
|
175
|
+
async def serve_local_review():
|
|
176
|
+
"""Serve pre-rendered local review page with patched chunk loading."""
|
|
177
|
+
from fastapi.responses import HTMLResponse
|
|
178
|
+
local_review_html = os.path.join(frontend_dir, "app", "jobs", "local", "review", "index.html")
|
|
179
|
+
if os.path.exists(local_review_html):
|
|
180
|
+
# Read the HTML and inject the missing chunk script
|
|
181
|
+
# This works around a Turbopack static export issue where the RSC stream
|
|
182
|
+
# references module chunks that don't contain the actual code
|
|
183
|
+
with open(local_review_html, 'r', encoding='utf-8') as f:
|
|
184
|
+
html_content = f.read()
|
|
185
|
+
|
|
186
|
+
# Find the missing chunk that contains JobRouterClient (module 78280)
|
|
187
|
+
# The chunk name is determined at build time, so we need to find it dynamically
|
|
188
|
+
# We look for ",78280," which is the Turbopack module ID pattern
|
|
189
|
+
import glob
|
|
190
|
+
chunks_dir = os.path.join(frontend_dir, "_next", "static", "chunks")
|
|
191
|
+
for chunk_file in glob.glob(os.path.join(chunks_dir, "*.js")):
|
|
192
|
+
chunk_name = os.path.basename(chunk_file)
|
|
193
|
+
# Check if this chunk contains module 78280 (JobRouterClient)
|
|
194
|
+
with open(chunk_file, 'r', encoding='utf-8') as cf:
|
|
195
|
+
chunk_content = cf.read(500) # Module ID is near the start
|
|
196
|
+
if ",78280," in chunk_content:
|
|
197
|
+
# Inject this chunk script if not already present
|
|
198
|
+
script_tag = f'<script src="/_next/static/chunks/{chunk_name}" async=""></script>'
|
|
199
|
+
if chunk_name not in html_content:
|
|
200
|
+
# Insert before closing </head>
|
|
201
|
+
html_content = html_content.replace('</head>', f'{script_tag}</head>')
|
|
202
|
+
break
|
|
203
|
+
|
|
204
|
+
return HTMLResponse(content=html_content, media_type="text/html")
|
|
205
|
+
# Fallback to jobs index
|
|
206
|
+
jobs_html = os.path.join(frontend_dir, "app", "jobs", "index.html")
|
|
207
|
+
if os.path.exists(jobs_html):
|
|
208
|
+
return FileResponse(jobs_html, media_type="text/html")
|
|
209
|
+
raise HTTPException(status_code=404, detail="Review page not found")
|
|
210
|
+
|
|
211
|
+
# Job routes - serve the jobs page HTML for client-side routing
|
|
212
|
+
@self.app.get("/app/jobs/{path:path}")
|
|
213
|
+
async def serve_jobs_routes(path: str):
|
|
214
|
+
"""Serve jobs index.html for all /app/jobs/* routes (SPA routing)."""
|
|
215
|
+
jobs_html = os.path.join(frontend_dir, "app", "jobs", "index.html")
|
|
216
|
+
if os.path.exists(jobs_html):
|
|
217
|
+
return FileResponse(jobs_html, media_type="text/html")
|
|
218
|
+
raise HTTPException(status_code=404, detail="Jobs page not found")
|
|
219
|
+
|
|
220
|
+
# Other app routes - serve the app index.html
|
|
221
|
+
@self.app.get("/app/{path:path}")
|
|
222
|
+
async def serve_app_routes(path: str):
|
|
223
|
+
"""Serve app index.html for other /app/* routes."""
|
|
224
|
+
app_html = os.path.join(frontend_dir, "app", "index.html")
|
|
225
|
+
if os.path.exists(app_html):
|
|
226
|
+
return FileResponse(app_html, media_type="text/html")
|
|
227
|
+
# Fallback to root index.html
|
|
228
|
+
index_html = os.path.join(frontend_dir, "index.html")
|
|
229
|
+
if os.path.exists(index_html):
|
|
230
|
+
return FileResponse(index_html, media_type="text/html")
|
|
231
|
+
raise HTTPException(status_code=404, detail="Frontend not found")
|
|
232
|
+
|
|
233
|
+
# Favicon
|
|
234
|
+
@self.app.get("/favicon.ico")
|
|
235
|
+
async def serve_favicon():
|
|
236
|
+
favicon_path = os.path.join(frontend_dir, "favicon.ico")
|
|
237
|
+
if os.path.exists(favicon_path):
|
|
238
|
+
return FileResponse(favicon_path)
|
|
239
|
+
raise HTTPException(status_code=404, detail="Not found")
|
|
147
240
|
|
|
148
241
|
def _register_routes(self) -> None:
|
|
149
242
|
"""Register API routes."""
|
|
243
|
+
# Legacy routes (for backward compatibility with old frontend)
|
|
150
244
|
self.app.add_api_route("/api/correction-data", self.get_correction_data, methods=["GET"])
|
|
151
245
|
self.app.add_api_route("/api/complete", self.complete_review, methods=["POST"])
|
|
152
246
|
self.app.add_api_route("/api/preview-video", self.generate_preview_video, methods=["POST"])
|
|
@@ -156,6 +250,17 @@ class ReviewServer:
|
|
|
156
250
|
self.app.add_api_route("/api/handlers", self.update_handlers, methods=["POST"])
|
|
157
251
|
self.app.add_api_route("/api/add-lyrics", self.add_lyrics, methods=["POST"])
|
|
158
252
|
|
|
253
|
+
# Cloud-compatible routes (for unified Next.js frontend)
|
|
254
|
+
# These use the same handlers but with cloud-style URL patterns
|
|
255
|
+
# Job ID is always "local" for local CLI mode
|
|
256
|
+
self.app.add_api_route("/api/jobs/{job_id}", self.get_local_job, methods=["GET"])
|
|
257
|
+
self.app.add_api_route("/api/jobs/{job_id}/corrections", self.get_correction_data, methods=["GET"])
|
|
258
|
+
self.app.add_api_route("/api/jobs/{job_id}/corrections", self.complete_review, methods=["PUT"])
|
|
259
|
+
self.app.add_api_route("/api/jobs/{job_id}/preview-video", self.generate_preview_video, methods=["POST"])
|
|
260
|
+
self.app.add_api_route("/api/jobs/{job_id}/handlers", self.update_handlers_cloud, methods=["PATCH"])
|
|
261
|
+
self.app.add_api_route("/api/jobs/{job_id}/lyrics", self.add_lyrics, methods=["POST"])
|
|
262
|
+
self.app.add_api_route("/api/jobs/{job_id}/annotations", self.post_annotation, methods=["POST"])
|
|
263
|
+
|
|
159
264
|
# Agentic AI v1 endpoints (contract-compliant scaffolds)
|
|
160
265
|
self.app.add_api_route("/api/v1/correction/agentic", self.post_correction_agentic, methods=["POST"])
|
|
161
266
|
self.app.add_api_route("/api/v1/correction/session/{session_id}", self.get_correction_session_v1, methods=["GET"])
|
|
@@ -163,16 +268,56 @@ class ReviewServer:
|
|
|
163
268
|
self.app.add_api_route("/api/v1/models", self.get_models_v1, methods=["GET"])
|
|
164
269
|
self.app.add_api_route("/api/v1/models", self.put_models_v1, methods=["PUT"])
|
|
165
270
|
self.app.add_api_route("/api/v1/metrics", self.get_metrics_v1, methods=["GET"])
|
|
166
|
-
|
|
271
|
+
|
|
167
272
|
# Annotation endpoints
|
|
168
273
|
self.app.add_api_route("/api/v1/annotations", self.post_annotation, methods=["POST"])
|
|
169
274
|
self.app.add_api_route("/api/v1/annotations/{audio_hash}", self.get_annotations_by_song, methods=["GET"])
|
|
170
275
|
self.app.add_api_route("/api/v1/annotations/stats", self.get_annotation_stats, methods=["GET"])
|
|
171
276
|
|
|
277
|
+
# Tenant config endpoint (returns default config for local mode)
|
|
278
|
+
self.app.add_api_route("/api/tenant/config", self.get_tenant_config, methods=["GET"])
|
|
279
|
+
|
|
172
280
|
async def get_correction_data(self):
|
|
173
281
|
"""Get the correction data."""
|
|
174
282
|
return self.correction_result.to_dict()
|
|
175
283
|
|
|
284
|
+
async def get_tenant_config(self):
|
|
285
|
+
"""Get tenant configuration for local mode.
|
|
286
|
+
|
|
287
|
+
Returns a default tenant config that enables all features
|
|
288
|
+
for local CLI mode.
|
|
289
|
+
"""
|
|
290
|
+
return {
|
|
291
|
+
"tenant": None,
|
|
292
|
+
"is_default": True
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async def get_local_job(self, job_id: str):
|
|
296
|
+
"""Get mock job data for local mode.
|
|
297
|
+
|
|
298
|
+
This endpoint returns job-like data that the unified frontend expects.
|
|
299
|
+
In local mode, the job_id is always "local".
|
|
300
|
+
"""
|
|
301
|
+
metadata = self.correction_result.metadata or {}
|
|
302
|
+
return {
|
|
303
|
+
"job_id": job_id,
|
|
304
|
+
"status": "awaiting_review",
|
|
305
|
+
"progress": 50,
|
|
306
|
+
"created_at": None,
|
|
307
|
+
"updated_at": None,
|
|
308
|
+
"artist": metadata.get("artist", "Local Artist"),
|
|
309
|
+
"title": metadata.get("title", "Local Title"),
|
|
310
|
+
"user_email": "local@localhost",
|
|
311
|
+
"audio_hash": metadata.get("audio_hash", "local"),
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async def update_handlers_cloud(self, job_id: str, enabled_handlers: List[str] = Body(...)):
|
|
315
|
+
"""Cloud-compatible handler update endpoint (PATCH method).
|
|
316
|
+
|
|
317
|
+
This wraps the existing update_handlers method with cloud-style routing.
|
|
318
|
+
"""
|
|
319
|
+
return await self.update_handlers(enabled_handlers)
|
|
320
|
+
|
|
176
321
|
# ------------------------------
|
|
177
322
|
# API v1: Agentic AI scaffolds
|
|
178
323
|
# ------------------------------
|
|
@@ -588,24 +733,27 @@ class ReviewServer:
|
|
|
588
733
|
server_thread = None
|
|
589
734
|
sock = None
|
|
590
735
|
|
|
736
|
+
# Get port from environment variable (default 8000)
|
|
737
|
+
port = int(os.environ.get("LYRICS_REVIEW_PORT", "8000"))
|
|
738
|
+
|
|
591
739
|
try:
|
|
592
740
|
# Check port availability
|
|
593
741
|
while True:
|
|
594
742
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
595
743
|
sock.settimeout(1)
|
|
596
|
-
if sock.connect_ex(("127.0.0.1",
|
|
744
|
+
if sock.connect_ex(("127.0.0.1", port)) == 0:
|
|
597
745
|
# Port is in use, get process info
|
|
598
746
|
process_info = ""
|
|
599
747
|
if os.name != "nt": # Unix-like systems
|
|
600
748
|
try:
|
|
601
|
-
process_info = os.popen("lsof -i:
|
|
749
|
+
process_info = os.popen(f"lsof -i:{port}").read().strip()
|
|
602
750
|
except:
|
|
603
751
|
pass
|
|
604
752
|
|
|
605
753
|
self.logger.warning(
|
|
606
|
-
f"Port
|
|
607
|
-
f"Process using port
|
|
608
|
-
f"To manually free the port, you can run: lsof -ti:
|
|
754
|
+
f"Port {port} is in use. Waiting for it to become available...\n"
|
|
755
|
+
f"Process using port {port}:\n{process_info}\n"
|
|
756
|
+
f"To manually free the port, you can run: lsof -ti:{port} | xargs kill -9"
|
|
609
757
|
)
|
|
610
758
|
sock.close()
|
|
611
759
|
time.sleep(30)
|
|
@@ -614,31 +762,15 @@ class ReviewServer:
|
|
|
614
762
|
break
|
|
615
763
|
|
|
616
764
|
# Start server
|
|
617
|
-
config = uvicorn.Config(self.app, host="127.0.0.1", port=
|
|
765
|
+
config = uvicorn.Config(self.app, host="127.0.0.1", port=port, log_level="error")
|
|
618
766
|
server = uvicorn.Server(config)
|
|
619
767
|
server_thread = Thread(target=server.run, daemon=True)
|
|
620
768
|
server_thread.start()
|
|
621
769
|
time.sleep(0.5) # Reduced wait time
|
|
622
770
|
|
|
623
|
-
# Open browser
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
audio_hash_param = (
|
|
627
|
-
f"&audioHash={self.correction_result.metadata.get('audio_hash', '')}"
|
|
628
|
-
if self.correction_result.metadata and "audio_hash" in self.correction_result.metadata
|
|
629
|
-
else ""
|
|
630
|
-
)
|
|
631
|
-
|
|
632
|
-
# Use bundled local frontend by default for local karaoke-gen runs
|
|
633
|
-
# Can override with LYRICS_REVIEW_UI_URL env var (e.g., http://localhost:5173 for Vite dev)
|
|
634
|
-
review_ui_url = os.environ.get("LYRICS_REVIEW_UI_URL", "local")
|
|
635
|
-
if review_ui_url.lower() == "local":
|
|
636
|
-
# Use the bundled local frontend served by this FastAPI server
|
|
637
|
-
browser_url = f"http://localhost:8000?baseApiUrl={encoded_api_url}{audio_hash_param}"
|
|
638
|
-
else:
|
|
639
|
-
# Use an external review UI (dev server or hosted)
|
|
640
|
-
browser_url = f"{review_ui_url}?baseApiUrl={encoded_api_url}{audio_hash_param}"
|
|
641
|
-
|
|
771
|
+
# Open browser to the Next.js review UI
|
|
772
|
+
# The frontend will automatically detect local mode and skip auth
|
|
773
|
+
browser_url = f"http://localhost:{port}/app/jobs/local/review"
|
|
642
774
|
self.logger.info(f"Opening review UI: {browser_url}")
|
|
643
775
|
webbrowser.open(browser_url)
|
|
644
776
|
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""
|
|
2
|
+
OpenTelemetry tracing utilities for lyrics_transcriber library.
|
|
3
|
+
|
|
4
|
+
This module provides tracing utilities that integrate with the backend's
|
|
5
|
+
OpenTelemetry setup. When running in the backend context (Cloud Run),
|
|
6
|
+
traces will export to Google Cloud Trace. When running standalone (CLI),
|
|
7
|
+
tracing is no-op unless explicitly configured.
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
from lyrics_transcriber.utils.tracing import create_span, traced
|
|
11
|
+
|
|
12
|
+
# Context manager for spans
|
|
13
|
+
with create_span("operation_name", {"key": "value"}) as span:
|
|
14
|
+
span.set_attribute("result_count", 42)
|
|
15
|
+
# ... do work ...
|
|
16
|
+
|
|
17
|
+
# Decorator for functions
|
|
18
|
+
@traced("custom_name")
|
|
19
|
+
def my_function():
|
|
20
|
+
pass
|
|
21
|
+
"""
|
|
22
|
+
import logging
|
|
23
|
+
from typing import Any, Dict, Optional
|
|
24
|
+
from contextlib import contextmanager
|
|
25
|
+
from functools import wraps
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
# Try to import OpenTelemetry - gracefully handle missing dependency
|
|
30
|
+
try:
|
|
31
|
+
from opentelemetry import trace
|
|
32
|
+
from opentelemetry.trace import Status, StatusCode, Span
|
|
33
|
+
OTEL_AVAILABLE = True
|
|
34
|
+
except ImportError:
|
|
35
|
+
OTEL_AVAILABLE = False
|
|
36
|
+
trace = None
|
|
37
|
+
Status = None
|
|
38
|
+
StatusCode = None
|
|
39
|
+
Span = None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def get_tracer(name: str = "lyrics_transcriber") -> Any:
|
|
43
|
+
"""
|
|
44
|
+
Get a tracer instance.
|
|
45
|
+
|
|
46
|
+
If OpenTelemetry is available and configured (by the backend),
|
|
47
|
+
returns a real tracer. Otherwise returns a no-op tracer.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
name: Tracer name (typically module name)
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Tracer instance
|
|
54
|
+
"""
|
|
55
|
+
if OTEL_AVAILABLE and trace:
|
|
56
|
+
return trace.get_tracer(name)
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@contextmanager
|
|
61
|
+
def create_span(
|
|
62
|
+
name: str,
|
|
63
|
+
attributes: Optional[Dict[str, Any]] = None,
|
|
64
|
+
tracer_name: str = "lyrics_transcriber",
|
|
65
|
+
):
|
|
66
|
+
"""
|
|
67
|
+
Create a traced span as a context manager.
|
|
68
|
+
|
|
69
|
+
If OpenTelemetry is not available or not configured, this is a no-op.
|
|
70
|
+
|
|
71
|
+
Usage:
|
|
72
|
+
with create_span("find_anchors", {"word_count": 500}) as span:
|
|
73
|
+
# ... do work ...
|
|
74
|
+
if span:
|
|
75
|
+
span.set_attribute("anchors_found", 42)
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
name: Span name (appears in Cloud Trace)
|
|
79
|
+
attributes: Initial span attributes
|
|
80
|
+
tracer_name: Tracer name to use
|
|
81
|
+
|
|
82
|
+
Yields:
|
|
83
|
+
The active span, or None if tracing is not available
|
|
84
|
+
"""
|
|
85
|
+
tracer = get_tracer(tracer_name)
|
|
86
|
+
|
|
87
|
+
if tracer is None:
|
|
88
|
+
yield None
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
with tracer.start_as_current_span(name) as span:
|
|
92
|
+
if attributes:
|
|
93
|
+
for key, value in attributes.items():
|
|
94
|
+
# Only set if value is a valid type
|
|
95
|
+
if isinstance(value, (str, int, float, bool)):
|
|
96
|
+
span.set_attribute(key, value)
|
|
97
|
+
elif isinstance(value, (list, tuple)) and all(isinstance(v, (str, int, float, bool)) for v in value):
|
|
98
|
+
span.set_attribute(key, list(value))
|
|
99
|
+
try:
|
|
100
|
+
yield span
|
|
101
|
+
except Exception as e:
|
|
102
|
+
if OTEL_AVAILABLE and Status and StatusCode:
|
|
103
|
+
span.set_status(Status(StatusCode.ERROR, str(e)))
|
|
104
|
+
span.record_exception(e)
|
|
105
|
+
raise
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def traced(
|
|
109
|
+
name: Optional[str] = None,
|
|
110
|
+
attributes: Optional[Dict[str, Any]] = None,
|
|
111
|
+
tracer_name: str = "lyrics_transcriber",
|
|
112
|
+
):
|
|
113
|
+
"""
|
|
114
|
+
Decorator to trace a function.
|
|
115
|
+
|
|
116
|
+
If OpenTelemetry is not available or not configured, this is a no-op decorator.
|
|
117
|
+
|
|
118
|
+
Usage:
|
|
119
|
+
@traced("process_correction")
|
|
120
|
+
def process_correction(job_id: str):
|
|
121
|
+
...
|
|
122
|
+
|
|
123
|
+
@traced(attributes={"operation": "add-lyrics"})
|
|
124
|
+
async def add_lyrics(job_id: str, source: str):
|
|
125
|
+
...
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
name: Span name (defaults to function name)
|
|
129
|
+
attributes: Static attributes to add to span
|
|
130
|
+
tracer_name: Tracer name to use
|
|
131
|
+
"""
|
|
132
|
+
def decorator(func):
|
|
133
|
+
span_name = name or func.__name__
|
|
134
|
+
|
|
135
|
+
@wraps(func)
|
|
136
|
+
def sync_wrapper(*args, **kwargs):
|
|
137
|
+
with create_span(span_name, attributes, tracer_name) as span:
|
|
138
|
+
if span:
|
|
139
|
+
span.set_attribute("function", func.__name__)
|
|
140
|
+
return func(*args, **kwargs)
|
|
141
|
+
|
|
142
|
+
@wraps(func)
|
|
143
|
+
async def async_wrapper(*args, **kwargs):
|
|
144
|
+
with create_span(span_name, attributes, tracer_name) as span:
|
|
145
|
+
if span:
|
|
146
|
+
span.set_attribute("function", func.__name__)
|
|
147
|
+
return await func(*args, **kwargs)
|
|
148
|
+
|
|
149
|
+
# Return appropriate wrapper based on function type
|
|
150
|
+
import asyncio
|
|
151
|
+
if asyncio.iscoroutinefunction(func):
|
|
152
|
+
return async_wrapper
|
|
153
|
+
return sync_wrapper
|
|
154
|
+
|
|
155
|
+
return decorator
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def add_span_attribute(key: str, value: Any) -> None:
|
|
159
|
+
"""
|
|
160
|
+
Add an attribute to the current span.
|
|
161
|
+
|
|
162
|
+
If no span is active or tracing is not available, this is a no-op.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
key: Attribute name
|
|
166
|
+
value: Attribute value (must be string, int, float, bool, or list thereof)
|
|
167
|
+
"""
|
|
168
|
+
if not OTEL_AVAILABLE or not trace:
|
|
169
|
+
return
|
|
170
|
+
|
|
171
|
+
span = trace.get_current_span()
|
|
172
|
+
if span and span.is_recording():
|
|
173
|
+
# Validate type
|
|
174
|
+
if isinstance(value, (str, int, float, bool)):
|
|
175
|
+
span.set_attribute(key, value)
|
|
176
|
+
elif isinstance(value, (list, tuple)) and all(isinstance(v, (str, int, float, bool)) for v in value):
|
|
177
|
+
span.set_attribute(key, list(value))
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def add_span_event(name: str, attributes: Optional[Dict[str, Any]] = None) -> None:
|
|
181
|
+
"""
|
|
182
|
+
Add an event to the current span.
|
|
183
|
+
|
|
184
|
+
Events are timestamped annotations that appear in the trace timeline.
|
|
185
|
+
If no span is active or tracing is not available, this is a no-op.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
name: Event name
|
|
189
|
+
attributes: Event attributes
|
|
190
|
+
"""
|
|
191
|
+
if not OTEL_AVAILABLE or not trace:
|
|
192
|
+
return
|
|
193
|
+
|
|
194
|
+
span = trace.get_current_span()
|
|
195
|
+
if span and span.is_recording():
|
|
196
|
+
span.add_event(name, attributes=attributes or {})
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def set_span_error(error: Exception) -> None:
|
|
200
|
+
"""
|
|
201
|
+
Mark the current span as errored.
|
|
202
|
+
|
|
203
|
+
If no span is active or tracing is not available, this is a no-op.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
error: The exception that occurred
|
|
207
|
+
"""
|
|
208
|
+
if not OTEL_AVAILABLE or not trace or not Status or not StatusCode:
|
|
209
|
+
return
|
|
210
|
+
|
|
211
|
+
span = trace.get_current_span()
|
|
212
|
+
if span and span.is_recording():
|
|
213
|
+
span.set_status(Status(StatusCode.ERROR, str(error)))
|
|
214
|
+
span.record_exception(error)
|