karaoke-gen 0.105.4__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/api/routes/users.py +14 -3
- backend/config.py +3 -0
- backend/services/encoding_interface.py +4 -0
- backend/services/job_notification_service.py +4 -21
- backend/tests/test_job_notification_service.py +24 -58
- backend/tests/test_video_worker_orchestrator.py +189 -0
- backend/workers/video_worker_orchestrator.py +7 -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.105.4.dist-info → karaoke_gen-0.107.0.dist-info}/METADATA +1 -1
- {karaoke_gen-0.105.4.dist-info → karaoke_gen-0.107.0.dist-info}/RECORD +227 -121
- {karaoke_gen-0.105.4.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 -1721
- 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.105.4.dist-info → karaoke_gen-0.107.0.dist-info}/entry_points.txt +0 -0
- {karaoke_gen-0.105.4.dist-info → karaoke_gen-0.107.0.dist-info}/licenses/LICENSE +0 -0
backend/api/routes/users.py
CHANGED
|
@@ -858,8 +858,19 @@ async def enroll_beta_tester(
|
|
|
858
858
|
detail="Access denied from this location"
|
|
859
859
|
)
|
|
860
860
|
|
|
861
|
-
# 3. Check
|
|
862
|
-
|
|
861
|
+
# 3. Check for E2E test bypass (allows automated testing to skip IP rate limit)
|
|
862
|
+
from backend.config import settings
|
|
863
|
+
e2e_bypass_key = http_request.headers.get("X-E2E-Bypass-Key")
|
|
864
|
+
skip_ip_rate_limit = False
|
|
865
|
+
if e2e_bypass_key and settings.e2e_bypass_key:
|
|
866
|
+
if e2e_bypass_key == settings.e2e_bypass_key:
|
|
867
|
+
logger.info(f"Beta enrollment: E2E bypass key validated for {_mask_email(email)}")
|
|
868
|
+
skip_ip_rate_limit = True
|
|
869
|
+
else:
|
|
870
|
+
logger.warning(f"Beta enrollment: Invalid E2E bypass key attempted for {_mask_email(email)}")
|
|
871
|
+
|
|
872
|
+
# 4. Check IP-based enrollment rate limit (1 per 24h per IP)
|
|
873
|
+
if ip_address and not skip_ip_rate_limit:
|
|
863
874
|
allowed, remaining, message = rate_limit_service.check_beta_ip_limit(ip_address)
|
|
864
875
|
if not allowed:
|
|
865
876
|
logger.warning(f"Beta enrollment rejected - IP rate limit: {ip_address} - {message}")
|
|
@@ -868,7 +879,7 @@ async def enroll_beta_tester(
|
|
|
868
879
|
detail="Too many beta enrollments from your location. Please try again tomorrow."
|
|
869
880
|
)
|
|
870
881
|
|
|
871
|
-
#
|
|
882
|
+
# 5. Check for duplicate enrollment via normalized email
|
|
872
883
|
normalized_email = email_validation.normalize_email(email)
|
|
873
884
|
if normalized_email != email:
|
|
874
885
|
# Check if normalized version is already enrolled
|
backend/config.py
CHANGED
|
@@ -111,6 +111,9 @@ class Settings(BaseSettings):
|
|
|
111
111
|
rate_limit_youtube_uploads_per_day: int = int(os.getenv("RATE_LIMIT_YOUTUBE_UPLOADS_PER_DAY", "10"))
|
|
112
112
|
# Maximum beta enrollments from same IP per day (0 = unlimited)
|
|
113
113
|
rate_limit_beta_ip_per_day: int = int(os.getenv("RATE_LIMIT_BETA_IP_PER_DAY", "1"))
|
|
114
|
+
|
|
115
|
+
# E2E test bypass key for rate limiting (set via secret in production)
|
|
116
|
+
e2e_bypass_key: str = os.getenv("E2E_BYPASS_KEY", "")
|
|
114
117
|
default_youtube_description: str = os.getenv(
|
|
115
118
|
"DEFAULT_YOUTUBE_DESCRIPTION",
|
|
116
119
|
"Karaoke video created with Nomad Karaoke (https://nomadkaraoke.com)\n\n"
|
|
@@ -40,6 +40,9 @@ class EncodingInput:
|
|
|
40
40
|
# Output directory
|
|
41
41
|
output_dir: str = ""
|
|
42
42
|
|
|
43
|
+
# Instrumental selection (clean, with_backing, or custom)
|
|
44
|
+
instrumental_selection: str = "clean"
|
|
45
|
+
|
|
43
46
|
# Additional options
|
|
44
47
|
options: Dict[str, Any] = field(default_factory=dict)
|
|
45
48
|
|
|
@@ -328,6 +331,7 @@ class GCEEncodingBackend(EncodingBackend):
|
|
|
328
331
|
"formats": ["mp4_4k_lossless", "mp4_4k_lossy", "mkv_4k", "mp4_720p"],
|
|
329
332
|
"artist": input_config.artist,
|
|
330
333
|
"title": input_config.title,
|
|
334
|
+
"instrumental_selection": input_config.instrumental_selection,
|
|
331
335
|
}
|
|
332
336
|
|
|
333
337
|
# Submit and wait for completion
|
|
@@ -53,30 +53,13 @@ class JobNotificationService:
|
|
|
53
53
|
|
|
54
54
|
def _build_review_url(self, job_id: str, audio_hash: Optional[str] = None, review_token: Optional[str] = None) -> str:
|
|
55
55
|
"""Build the lyrics review URL for a job."""
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
review_ui_url = os.getenv("REVIEW_UI_URL", f"{self.frontend_url}/lyrics/")
|
|
59
|
-
base_api_url = f"{self.backend_url}/api/review/{job_id}"
|
|
60
|
-
encoded_api_url = urllib.parse.quote(base_api_url, safe='')
|
|
61
|
-
|
|
62
|
-
url = f"{review_ui_url}?baseApiUrl={encoded_api_url}"
|
|
63
|
-
if audio_hash:
|
|
64
|
-
url += f"&audioHash={urllib.parse.quote(audio_hash, safe='')}"
|
|
65
|
-
if review_token:
|
|
66
|
-
url += f"&reviewToken={urllib.parse.quote(review_token, safe='')}"
|
|
67
|
-
return url
|
|
56
|
+
# Use consolidated frontend route
|
|
57
|
+
return f"{self.frontend_url}/app/jobs/{job_id}/review"
|
|
68
58
|
|
|
69
59
|
def _build_instrumental_url(self, job_id: str, instrumental_token: Optional[str] = None) -> str:
|
|
70
60
|
"""Build the instrumental selection URL for a job."""
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
import urllib.parse
|
|
74
|
-
encoded_api_url = urllib.parse.quote(base_api_url, safe='')
|
|
75
|
-
|
|
76
|
-
url = f"{self.frontend_url}/instrumental/?baseApiUrl={encoded_api_url}"
|
|
77
|
-
if instrumental_token:
|
|
78
|
-
url += f"&instrumentalToken={urllib.parse.quote(instrumental_token, safe='')}"
|
|
79
|
-
return url
|
|
61
|
+
# Use consolidated frontend route
|
|
62
|
+
return f"{self.frontend_url}/app/jobs/{job_id}/instrumental"
|
|
80
63
|
|
|
81
64
|
async def send_job_completion_email(
|
|
82
65
|
self,
|
|
@@ -3,7 +3,6 @@ Unit tests for job notification service.
|
|
|
3
3
|
"""
|
|
4
4
|
import pytest
|
|
5
5
|
from unittest.mock import Mock, patch, AsyncMock
|
|
6
|
-
import urllib.parse
|
|
7
6
|
|
|
8
7
|
from backend.services.job_notification_service import (
|
|
9
8
|
JobNotificationService,
|
|
@@ -20,82 +19,55 @@ class TestURLBuilding:
|
|
|
20
19
|
"""Test basic review URL building."""
|
|
21
20
|
service = JobNotificationService()
|
|
22
21
|
service.frontend_url = "https://gen.nomadkaraoke.com"
|
|
23
|
-
service.backend_url = "https://api.nomadkaraoke.com"
|
|
24
22
|
|
|
25
23
|
url = service._build_review_url("job-123")
|
|
26
24
|
|
|
27
|
-
assert "gen.nomadkaraoke.com/
|
|
28
|
-
assert "baseApiUrl=" in url
|
|
29
|
-
# The API URL should be URL-encoded
|
|
30
|
-
assert urllib.parse.quote("https://api.nomadkaraoke.com/api/review/job-123", safe='') in url
|
|
25
|
+
assert url == "https://gen.nomadkaraoke.com/app/jobs/job-123/review"
|
|
31
26
|
|
|
32
|
-
def
|
|
33
|
-
"""Test review URL
|
|
27
|
+
def test_build_review_url_ignores_legacy_params(self):
|
|
28
|
+
"""Test review URL ignores legacy audio_hash and review_token params."""
|
|
34
29
|
service = JobNotificationService()
|
|
35
30
|
service.frontend_url = "https://gen.nomadkaraoke.com"
|
|
36
|
-
service.backend_url = "https://api.nomadkaraoke.com"
|
|
37
31
|
|
|
38
|
-
|
|
32
|
+
# Legacy params are still accepted but not used in the URL
|
|
33
|
+
url = service._build_review_url("job-123", audio_hash="abc123", review_token="token456")
|
|
39
34
|
|
|
40
|
-
|
|
35
|
+
# URL should be the simple consolidated route
|
|
36
|
+
assert url == "https://gen.nomadkaraoke.com/app/jobs/job-123/review"
|
|
37
|
+
# No query params
|
|
38
|
+
assert "?" not in url
|
|
41
39
|
|
|
42
|
-
def
|
|
43
|
-
"""Test
|
|
40
|
+
def test_build_review_url_preserves_job_id_characters(self):
|
|
41
|
+
"""Test that job ID is used directly in URL path."""
|
|
44
42
|
service = JobNotificationService()
|
|
45
43
|
service.frontend_url = "https://gen.nomadkaraoke.com"
|
|
46
|
-
service.backend_url = "https://api.nomadkaraoke.com"
|
|
47
44
|
|
|
48
|
-
|
|
45
|
+
# Note: job IDs with slashes would be unusual in real usage
|
|
46
|
+
url = service._build_review_url("abc-def-123")
|
|
49
47
|
|
|
50
|
-
assert "
|
|
51
|
-
|
|
52
|
-
def test_build_review_url_with_all_params(self):
|
|
53
|
-
"""Test review URL with all parameters."""
|
|
54
|
-
service = JobNotificationService()
|
|
55
|
-
service.frontend_url = "https://gen.nomadkaraoke.com"
|
|
56
|
-
service.backend_url = "https://api.nomadkaraoke.com"
|
|
57
|
-
|
|
58
|
-
url = service._build_review_url(
|
|
59
|
-
"job-123",
|
|
60
|
-
audio_hash="hash789",
|
|
61
|
-
review_token="token456"
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
assert "baseApiUrl=" in url
|
|
65
|
-
assert "audioHash=hash789" in url
|
|
66
|
-
assert "reviewToken=token456" in url
|
|
67
|
-
|
|
68
|
-
def test_build_review_url_encodes_special_chars(self):
|
|
69
|
-
"""Test that special characters in job ID are encoded."""
|
|
70
|
-
service = JobNotificationService()
|
|
71
|
-
service.frontend_url = "https://gen.nomadkaraoke.com"
|
|
72
|
-
service.backend_url = "https://api.nomadkaraoke.com"
|
|
73
|
-
|
|
74
|
-
url = service._build_review_url("job/with/slashes")
|
|
75
|
-
|
|
76
|
-
# The baseApiUrl parameter should have encoded slashes
|
|
77
|
-
assert "%2F" in url
|
|
48
|
+
assert url == "https://gen.nomadkaraoke.com/app/jobs/abc-def-123/review"
|
|
78
49
|
|
|
79
50
|
def test_build_instrumental_url_basic(self):
|
|
80
51
|
"""Test basic instrumental URL building."""
|
|
81
52
|
service = JobNotificationService()
|
|
82
53
|
service.frontend_url = "https://gen.nomadkaraoke.com"
|
|
83
|
-
service.backend_url = "https://api.nomadkaraoke.com"
|
|
84
54
|
|
|
85
55
|
url = service._build_instrumental_url("job-123")
|
|
86
56
|
|
|
87
|
-
assert "gen.nomadkaraoke.com/instrumental
|
|
88
|
-
assert "baseApiUrl=" in url
|
|
57
|
+
assert url == "https://gen.nomadkaraoke.com/app/jobs/job-123/instrumental"
|
|
89
58
|
|
|
90
|
-
def
|
|
91
|
-
"""Test instrumental URL
|
|
59
|
+
def test_build_instrumental_url_ignores_legacy_params(self):
|
|
60
|
+
"""Test instrumental URL ignores legacy instrumental_token param."""
|
|
92
61
|
service = JobNotificationService()
|
|
93
62
|
service.frontend_url = "https://gen.nomadkaraoke.com"
|
|
94
|
-
service.backend_url = "https://api.nomadkaraoke.com"
|
|
95
63
|
|
|
64
|
+
# Legacy param is still accepted but not used in the URL
|
|
96
65
|
url = service._build_instrumental_url("job-123", instrumental_token="inst-token")
|
|
97
66
|
|
|
98
|
-
|
|
67
|
+
# URL should be the simple consolidated route
|
|
68
|
+
assert url == "https://gen.nomadkaraoke.com/app/jobs/job-123/instrumental"
|
|
69
|
+
# No query params
|
|
70
|
+
assert "?" not in url
|
|
99
71
|
|
|
100
72
|
|
|
101
73
|
class TestCompletionEmail:
|
|
@@ -325,7 +297,6 @@ class TestActionReminderEmail:
|
|
|
325
297
|
"""Test that lyrics reminder includes correct review URL."""
|
|
326
298
|
service = JobNotificationService()
|
|
327
299
|
service.frontend_url = "https://gen.nomadkaraoke.com"
|
|
328
|
-
service.backend_url = "https://api.nomadkaraoke.com"
|
|
329
300
|
service.email_service = Mock()
|
|
330
301
|
service.email_service.send_action_reminder.return_value = True
|
|
331
302
|
service.template_service = Mock()
|
|
@@ -336,22 +307,18 @@ class TestActionReminderEmail:
|
|
|
336
307
|
job_id="job-123",
|
|
337
308
|
user_email="user@example.com",
|
|
338
309
|
action_type="lyrics",
|
|
339
|
-
audio_hash="hash123",
|
|
340
|
-
review_token="token456",
|
|
341
310
|
)
|
|
342
311
|
|
|
343
312
|
# Verify the review URL was passed to template
|
|
344
313
|
call_kwargs = service.template_service.render_action_needed_lyrics.call_args.kwargs
|
|
345
314
|
review_url = call_kwargs.get('review_url')
|
|
346
|
-
assert "
|
|
347
|
-
assert "reviewToken=token456" in review_url
|
|
315
|
+
assert review_url == "https://gen.nomadkaraoke.com/app/jobs/job-123/review"
|
|
348
316
|
|
|
349
317
|
@pytest.mark.asyncio
|
|
350
318
|
async def test_send_instrumental_reminder_includes_url(self):
|
|
351
319
|
"""Test that instrumental reminder includes correct URL."""
|
|
352
320
|
service = JobNotificationService()
|
|
353
321
|
service.frontend_url = "https://gen.nomadkaraoke.com"
|
|
354
|
-
service.backend_url = "https://api.nomadkaraoke.com"
|
|
355
322
|
service.email_service = Mock()
|
|
356
323
|
service.email_service.send_action_reminder.return_value = True
|
|
357
324
|
service.template_service = Mock()
|
|
@@ -362,13 +329,12 @@ class TestActionReminderEmail:
|
|
|
362
329
|
job_id="job-123",
|
|
363
330
|
user_email="user@example.com",
|
|
364
331
|
action_type="instrumental",
|
|
365
|
-
instrumental_token="inst-token",
|
|
366
332
|
)
|
|
367
333
|
|
|
368
334
|
# Verify the instrumental URL was passed to template
|
|
369
335
|
call_kwargs = service.template_service.render_action_needed_instrumental.call_args.kwargs
|
|
370
336
|
instrumental_url = call_kwargs.get('instrumental_url')
|
|
371
|
-
assert "
|
|
337
|
+
assert instrumental_url == "https://gen.nomadkaraoke.com/app/jobs/job-123/instrumental"
|
|
372
338
|
|
|
373
339
|
|
|
374
340
|
class TestGetCompletionMessage:
|
|
@@ -845,3 +845,192 @@ class TestCreateOrchestratorConfigFromJob:
|
|
|
845
845
|
)
|
|
846
846
|
|
|
847
847
|
assert config.instrumental_audio_path == "/tmp/test/Test Artist - Test Title (Instrumental User).mp3"
|
|
848
|
+
|
|
849
|
+
def test_create_config_passes_instrumental_selection(self):
|
|
850
|
+
"""Test that instrumental_selection is passed through to OrchestratorConfig.
|
|
851
|
+
|
|
852
|
+
This is a REGRESSION TEST for the bug where orchestrator -> GCE encoding
|
|
853
|
+
path did not pass instrumental_selection, causing GCE worker to default
|
|
854
|
+
to 'clean' even when user selected 'with_backing'.
|
|
855
|
+
|
|
856
|
+
The bug was:
|
|
857
|
+
- PR #271 fixed GCE worker to READ instrumental_selection from config
|
|
858
|
+
- But the orchestrator path (encoding_interface.py) was never updated to SEND it
|
|
859
|
+
- The legacy path (video_worker.py _encode_via_gce) was already correct
|
|
860
|
+
- So the bug only manifested when USE_NEW_ORCHESTRATOR=true (the default)
|
|
861
|
+
|
|
862
|
+
See: fix(gce): Respect user's instrumental selection in GCE encoding worker (#271)
|
|
863
|
+
"""
|
|
864
|
+
job = MagicMock()
|
|
865
|
+
job.job_id = "test-123"
|
|
866
|
+
job.artist = "Test Artist"
|
|
867
|
+
job.title = "Test Title"
|
|
868
|
+
job.state_data = {"instrumental_selection": "with_backing"} # User selected backing vocals
|
|
869
|
+
job.enable_cdg = False
|
|
870
|
+
job.enable_txt = False
|
|
871
|
+
job.enable_youtube_upload = False
|
|
872
|
+
job.brand_prefix = None
|
|
873
|
+
job.discord_webhook_url = None
|
|
874
|
+
job.youtube_description_template = None
|
|
875
|
+
job.dropbox_path = None
|
|
876
|
+
job.gdrive_folder_id = None
|
|
877
|
+
job.keep_brand_code = None
|
|
878
|
+
job.existing_instrumental_gcs_path = None
|
|
879
|
+
|
|
880
|
+
config = create_orchestrator_config_from_job(
|
|
881
|
+
job=job,
|
|
882
|
+
temp_dir="/tmp/test",
|
|
883
|
+
)
|
|
884
|
+
|
|
885
|
+
# CRITICAL: instrumental_selection must be passed to OrchestratorConfig
|
|
886
|
+
# If this fails, the GCE worker will default to 'clean' and ignore user's selection
|
|
887
|
+
assert config.instrumental_selection == "with_backing", \
|
|
888
|
+
"instrumental_selection must be passed from job.state_data to OrchestratorConfig"
|
|
889
|
+
|
|
890
|
+
# Also verify the instrumental path uses "Backing" not "Clean"
|
|
891
|
+
assert "Backing" in config.instrumental_audio_path, \
|
|
892
|
+
"When with_backing is selected, instrumental path should contain 'Backing'"
|
|
893
|
+
|
|
894
|
+
|
|
895
|
+
class TestInstrumentalSelectionEndToEnd:
|
|
896
|
+
"""End-to-end tests for instrumental selection flow.
|
|
897
|
+
|
|
898
|
+
These tests verify that instrumental_selection flows correctly from:
|
|
899
|
+
job.state_data -> OrchestratorConfig -> EncodingInput -> GCE encoding_config
|
|
900
|
+
|
|
901
|
+
This test class was added after discovering that PR #271 only fixed the
|
|
902
|
+
GCE worker (receiving side) but not the orchestrator (sending side),
|
|
903
|
+
causing the bug to persist in production where USE_NEW_ORCHESTRATOR=true.
|
|
904
|
+
"""
|
|
905
|
+
|
|
906
|
+
def test_encoding_input_has_instrumental_selection_field(self):
|
|
907
|
+
"""Test that EncodingInput dataclass includes instrumental_selection.
|
|
908
|
+
|
|
909
|
+
Without this field, the orchestrator cannot pass the selection to
|
|
910
|
+
the encoding backend.
|
|
911
|
+
"""
|
|
912
|
+
from backend.services.encoding_interface import EncodingInput
|
|
913
|
+
|
|
914
|
+
# Test with explicit selection
|
|
915
|
+
input_with_backing = EncodingInput(
|
|
916
|
+
title_video_path="/path/title.mov",
|
|
917
|
+
karaoke_video_path="/path/karaoke.mov",
|
|
918
|
+
instrumental_audio_path="/path/audio.flac",
|
|
919
|
+
instrumental_selection="with_backing",
|
|
920
|
+
)
|
|
921
|
+
assert input_with_backing.instrumental_selection == "with_backing"
|
|
922
|
+
|
|
923
|
+
# Test default value
|
|
924
|
+
input_default = EncodingInput(
|
|
925
|
+
title_video_path="/path/title.mov",
|
|
926
|
+
karaoke_video_path="/path/karaoke.mov",
|
|
927
|
+
instrumental_audio_path="/path/audio.flac",
|
|
928
|
+
)
|
|
929
|
+
assert input_default.instrumental_selection == "clean", \
|
|
930
|
+
"Default instrumental_selection should be 'clean' for backward compatibility"
|
|
931
|
+
|
|
932
|
+
def test_orchestrator_config_has_instrumental_selection_field(self):
|
|
933
|
+
"""Test that OrchestratorConfig includes instrumental_selection."""
|
|
934
|
+
config = OrchestratorConfig(
|
|
935
|
+
job_id="test-job",
|
|
936
|
+
artist="Test Artist",
|
|
937
|
+
title="Test Title",
|
|
938
|
+
title_video_path="/path/title.mov",
|
|
939
|
+
karaoke_video_path="/path/karaoke.mov",
|
|
940
|
+
instrumental_audio_path="/path/audio.flac",
|
|
941
|
+
instrumental_selection="with_backing",
|
|
942
|
+
)
|
|
943
|
+
assert config.instrumental_selection == "with_backing"
|
|
944
|
+
|
|
945
|
+
# Test default
|
|
946
|
+
config_default = OrchestratorConfig(
|
|
947
|
+
job_id="test-job",
|
|
948
|
+
artist="Test Artist",
|
|
949
|
+
title="Test Title",
|
|
950
|
+
title_video_path="/path/title.mov",
|
|
951
|
+
karaoke_video_path="/path/karaoke.mov",
|
|
952
|
+
instrumental_audio_path="/path/audio.flac",
|
|
953
|
+
)
|
|
954
|
+
assert config_default.instrumental_selection == "clean"
|
|
955
|
+
|
|
956
|
+
def test_gce_encoding_config_includes_instrumental_selection(self):
|
|
957
|
+
"""Test that GCEEncodingBackend passes instrumental_selection to encoding_config.
|
|
958
|
+
|
|
959
|
+
This is the CRITICAL test that would have caught the bug in PR #271.
|
|
960
|
+
The GCE worker reads config.get("instrumental_selection", "clean"),
|
|
961
|
+
so if we don't send it, it defaults to 'clean' regardless of user selection.
|
|
962
|
+
"""
|
|
963
|
+
from backend.services.encoding_interface import EncodingInput, GCEEncodingBackend
|
|
964
|
+
|
|
965
|
+
backend = GCEEncodingBackend(dry_run=True)
|
|
966
|
+
|
|
967
|
+
# Create input with 'with_backing' selection
|
|
968
|
+
encoding_input = EncodingInput(
|
|
969
|
+
title_video_path="/path/title.mov",
|
|
970
|
+
karaoke_video_path="/path/karaoke.mov",
|
|
971
|
+
instrumental_audio_path="/path/audio.flac",
|
|
972
|
+
artist="Test Artist",
|
|
973
|
+
title="Test Title",
|
|
974
|
+
instrumental_selection="with_backing",
|
|
975
|
+
options={
|
|
976
|
+
"job_id": "test-123",
|
|
977
|
+
"input_gcs_path": "gs://bucket/jobs/test-123/",
|
|
978
|
+
"output_gcs_path": "gs://bucket/jobs/test-123/finals/",
|
|
979
|
+
},
|
|
980
|
+
)
|
|
981
|
+
|
|
982
|
+
# We can't easily test the actual encoding_config dict without mocking
|
|
983
|
+
# the service, but we can verify the input has the right value
|
|
984
|
+
assert encoding_input.instrumental_selection == "with_backing"
|
|
985
|
+
|
|
986
|
+
# The fix ensures GCEEncodingBackend.encode() includes this in encoding_config:
|
|
987
|
+
# encoding_config = {
|
|
988
|
+
# ...
|
|
989
|
+
# "instrumental_selection": input_config.instrumental_selection,
|
|
990
|
+
# }
|
|
991
|
+
|
|
992
|
+
@pytest.mark.asyncio
|
|
993
|
+
async def test_orchestrator_passes_instrumental_selection_to_encoding(self):
|
|
994
|
+
"""Test full flow: orchestrator creates EncodingInput with instrumental_selection.
|
|
995
|
+
|
|
996
|
+
This integration test verifies the complete path:
|
|
997
|
+
OrchestratorConfig.instrumental_selection -> EncodingInput.instrumental_selection
|
|
998
|
+
"""
|
|
999
|
+
config = OrchestratorConfig(
|
|
1000
|
+
job_id="test-job",
|
|
1001
|
+
artist="Test Artist",
|
|
1002
|
+
title="Test Title",
|
|
1003
|
+
title_video_path="/path/title.mov",
|
|
1004
|
+
karaoke_video_path="/path/karaoke.mov",
|
|
1005
|
+
instrumental_audio_path="/path/audio.flac",
|
|
1006
|
+
output_dir="/output",
|
|
1007
|
+
instrumental_selection="with_backing",
|
|
1008
|
+
)
|
|
1009
|
+
orchestrator = VideoWorkerOrchestrator(config)
|
|
1010
|
+
|
|
1011
|
+
# Capture the EncodingInput that gets passed to the backend
|
|
1012
|
+
captured_input = None
|
|
1013
|
+
|
|
1014
|
+
async def capture_encode(encoding_input):
|
|
1015
|
+
nonlocal captured_input
|
|
1016
|
+
captured_input = encoding_input
|
|
1017
|
+
from backend.services.encoding_interface import EncodingOutput
|
|
1018
|
+
return EncodingOutput(
|
|
1019
|
+
success=True,
|
|
1020
|
+
lossless_4k_mp4_path="/output/lossless.mp4",
|
|
1021
|
+
encoding_time_seconds=1.0,
|
|
1022
|
+
encoding_backend="mock",
|
|
1023
|
+
)
|
|
1024
|
+
|
|
1025
|
+
with patch.object(orchestrator, "_get_encoding_backend") as mock_get:
|
|
1026
|
+
mock_backend = MagicMock()
|
|
1027
|
+
mock_backend.name = "mock"
|
|
1028
|
+
mock_backend.encode = capture_encode
|
|
1029
|
+
mock_get.return_value = mock_backend
|
|
1030
|
+
|
|
1031
|
+
await orchestrator._run_encoding()
|
|
1032
|
+
|
|
1033
|
+
# CRITICAL ASSERTION: instrumental_selection must be passed through
|
|
1034
|
+
assert captured_input is not None, "encode() should have been called"
|
|
1035
|
+
assert captured_input.instrumental_selection == "with_backing", \
|
|
1036
|
+
"Orchestrator must pass instrumental_selection to EncodingInput"
|
|
@@ -69,6 +69,9 @@ class OrchestratorConfig:
|
|
|
69
69
|
# Keep existing brand code (for re-processing)
|
|
70
70
|
keep_brand_code: Optional[str] = None
|
|
71
71
|
|
|
72
|
+
# Instrumental selection (clean, with_backing, or custom)
|
|
73
|
+
instrumental_selection: str = "clean"
|
|
74
|
+
|
|
72
75
|
# Encoding backend preference
|
|
73
76
|
encoding_backend: str = "auto" # "auto", "local", "gce"
|
|
74
77
|
|
|
@@ -347,6 +350,7 @@ class VideoWorkerOrchestrator:
|
|
|
347
350
|
title=self.config.title,
|
|
348
351
|
brand_code=self.config.keep_brand_code,
|
|
349
352
|
output_dir=self.config.output_dir,
|
|
353
|
+
instrumental_selection=self.config.instrumental_selection,
|
|
350
354
|
options={
|
|
351
355
|
"job_id": self.config.job_id,
|
|
352
356
|
"input_gcs_path": input_gcs_path,
|
|
@@ -734,6 +738,9 @@ def create_orchestrator_config_from_job(
|
|
|
734
738
|
# Keep existing brand code
|
|
735
739
|
keep_brand_code=getattr(job, 'keep_brand_code', None),
|
|
736
740
|
|
|
741
|
+
# Instrumental selection (for GCE encoding)
|
|
742
|
+
instrumental_selection=instrumental_selection,
|
|
743
|
+
|
|
737
744
|
# Encoding backend - auto selects GCE if available
|
|
738
745
|
encoding_backend="auto",
|
|
739
746
|
|