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.
Files changed (322) hide show
  1. backend/api/routes/users.py +14 -3
  2. backend/config.py +3 -0
  3. backend/services/encoding_interface.py +4 -0
  4. backend/services/job_notification_service.py +4 -21
  5. backend/tests/test_job_notification_service.py +24 -58
  6. backend/tests/test_video_worker_orchestrator.py +189 -0
  7. backend/workers/video_worker_orchestrator.py +7 -0
  8. karaoke_gen/instrumental_review/server.py +145 -35
  9. karaoke_gen/nextjs_frontend/__init__.py +98 -0
  10. karaoke_gen/nextjs_frontend/out/404/index.html +1 -0
  11. karaoke_gen/nextjs_frontend/out/404.html +1 -0
  12. karaoke_gen/nextjs_frontend/out/__next.__PAGE__.txt +9 -0
  13. karaoke_gen/nextjs_frontend/out/__next._full.txt +22 -0
  14. karaoke_gen/nextjs_frontend/out/__next._head.txt +8 -0
  15. karaoke_gen/nextjs_frontend/out/__next._index.txt +9 -0
  16. karaoke_gen/nextjs_frontend/out/__next._tree.txt +2 -0
  17. karaoke_gen/nextjs_frontend/out/_next/static/chunks/01a7f8fe40f1ff47.js +1 -0
  18. karaoke_gen/nextjs_frontend/out/_next/static/chunks/112f346e31f991df.js +4 -0
  19. karaoke_gen/nextjs_frontend/out/_next/static/chunks/16d1a4dd9d8a873a.js +3 -0
  20. karaoke_gen/nextjs_frontend/out/_next/static/chunks/1ab85c362b8b0e86.js +9 -0
  21. karaoke_gen/nextjs_frontend/out/_next/static/chunks/247eb132b7f7b574.js +1 -0
  22. karaoke_gen/nextjs_frontend/out/_next/static/chunks/2b80d15cc95e4818.js +1 -0
  23. karaoke_gen/nextjs_frontend/out/_next/static/chunks/32c7eba5cd46c1bc.js +7 -0
  24. karaoke_gen/nextjs_frontend/out/_next/static/chunks/483f26794eae53d0.js +1 -0
  25. karaoke_gen/nextjs_frontend/out/_next/static/chunks/550c3b02e85f196a.js +1 -0
  26. karaoke_gen/nextjs_frontend/out/_next/static/chunks/55c5ade44387bef8.js +1 -0
  27. karaoke_gen/nextjs_frontend/out/_next/static/chunks/5628d92b5893add2.css +1 -0
  28. karaoke_gen/nextjs_frontend/out/_next/static/chunks/56ebf7665e4341c8.js +7 -0
  29. karaoke_gen/nextjs_frontend/out/_next/static/chunks/5997132b61dec430.js +1 -0
  30. karaoke_gen/nextjs_frontend/out/_next/static/chunks/5ea55255bce3eb9e.js +5 -0
  31. karaoke_gen/nextjs_frontend/out/_next/static/chunks/5eda89a57490b3cd.js +1 -0
  32. karaoke_gen/nextjs_frontend/out/_next/static/chunks/692f5d9e0d700c76.js +3 -0
  33. karaoke_gen/nextjs_frontend/out/_next/static/chunks/71d7a05b14f9f0f4.js +1 -0
  34. karaoke_gen/nextjs_frontend/out/_next/static/chunks/81ac355749ef3302.js +1 -0
  35. karaoke_gen/nextjs_frontend/out/_next/static/chunks/95f7e5934dbb0e5d.js +1 -0
  36. karaoke_gen/nextjs_frontend/out/_next/static/chunks/9bce8f19eaa46940.js +1 -0
  37. karaoke_gen/nextjs_frontend/out/_next/static/chunks/a6dad97d9634a72d.js +1 -0
  38. karaoke_gen/nextjs_frontend/out/_next/static/chunks/a9ed54eed3e14c92.js +2 -0
  39. karaoke_gen/nextjs_frontend/out/_next/static/chunks/b35cd41238ecfb17.js +1 -0
  40. karaoke_gen/nextjs_frontend/out/_next/static/chunks/b5bc3c3d5ebd49eb.js +1 -0
  41. karaoke_gen/nextjs_frontend/out/_next/static/chunks/b5c078c08db5ae32.js +5 -0
  42. karaoke_gen/nextjs_frontend/out/_next/static/chunks/be9c44a178104187.js +1 -0
  43. karaoke_gen/nextjs_frontend/out/_next/static/chunks/c4c840e18cb4861c.js +1 -0
  44. karaoke_gen/nextjs_frontend/out/_next/static/chunks/c645af7d6b65f73e.js +1 -0
  45. karaoke_gen/nextjs_frontend/out/_next/static/chunks/d2c5e2575df784d4.js +1 -0
  46. karaoke_gen/nextjs_frontend/out/_next/static/chunks/d30af02b96d81462.js +1 -0
  47. karaoke_gen/nextjs_frontend/out/_next/static/chunks/d9bdf64f4ec1e9b7.js +7 -0
  48. karaoke_gen/nextjs_frontend/out/_next/static/chunks/dcde6ed684dacd0e.js +5 -0
  49. karaoke_gen/nextjs_frontend/out/_next/static/chunks/e422cbe931246000.js +1 -0
  50. karaoke_gen/nextjs_frontend/out/_next/static/chunks/e483af34fc792d38.js +1 -0
  51. karaoke_gen/nextjs_frontend/out/_next/static/chunks/e57422aad6b897da.js +1 -0
  52. karaoke_gen/nextjs_frontend/out/_next/static/chunks/ef02697fb404726a.js +1 -0
  53. karaoke_gen/nextjs_frontend/out/_next/static/chunks/ff1a16fafef87110.js +1 -0
  54. karaoke_gen/nextjs_frontend/out/_next/static/chunks/turbopack-2d9ca3017a9deedf.js +3 -0
  55. karaoke_gen/nextjs_frontend/out/_next/static/zpw_-rjFIDV5tlPPtnvRI/_buildManifest.js +11 -0
  56. karaoke_gen/nextjs_frontend/out/_next/static/zpw_-rjFIDV5tlPPtnvRI/_clientMiddlewareManifest.json +1 -0
  57. karaoke_gen/nextjs_frontend/out/_next/static/zpw_-rjFIDV5tlPPtnvRI/_ssgManifest.js +1 -0
  58. karaoke_gen/nextjs_frontend/out/_not-found/__next._full.txt +18 -0
  59. karaoke_gen/nextjs_frontend/out/_not-found/__next._head.txt +8 -0
  60. karaoke_gen/nextjs_frontend/out/_not-found/__next._index.txt +9 -0
  61. karaoke_gen/nextjs_frontend/out/_not-found/__next._not-found.__PAGE__.txt +5 -0
  62. karaoke_gen/nextjs_frontend/out/_not-found/__next._not-found.txt +4 -0
  63. karaoke_gen/nextjs_frontend/out/_not-found/__next._tree.txt +2 -0
  64. karaoke_gen/nextjs_frontend/out/_not-found/index.html +1 -0
  65. karaoke_gen/nextjs_frontend/out/_not-found/index.txt +18 -0
  66. karaoke_gen/nextjs_frontend/out/admin/__next._full.txt +25 -0
  67. karaoke_gen/nextjs_frontend/out/admin/__next._head.txt +8 -0
  68. karaoke_gen/nextjs_frontend/out/admin/__next._index.txt +9 -0
  69. karaoke_gen/nextjs_frontend/out/admin/__next._tree.txt +2 -0
  70. karaoke_gen/nextjs_frontend/out/admin/__next.admin.__PAGE__.txt +9 -0
  71. karaoke_gen/nextjs_frontend/out/admin/__next.admin.txt +7 -0
  72. karaoke_gen/nextjs_frontend/out/admin/beta/__next._full.txt +25 -0
  73. karaoke_gen/nextjs_frontend/out/admin/beta/__next._head.txt +8 -0
  74. karaoke_gen/nextjs_frontend/out/admin/beta/__next._index.txt +9 -0
  75. karaoke_gen/nextjs_frontend/out/admin/beta/__next._tree.txt +2 -0
  76. karaoke_gen/nextjs_frontend/out/admin/beta/__next.admin.beta.__PAGE__.txt +9 -0
  77. karaoke_gen/nextjs_frontend/out/admin/beta/__next.admin.beta.txt +4 -0
  78. karaoke_gen/nextjs_frontend/out/admin/beta/__next.admin.txt +7 -0
  79. karaoke_gen/nextjs_frontend/out/admin/beta/index.html +1 -0
  80. karaoke_gen/nextjs_frontend/out/admin/beta/index.txt +25 -0
  81. karaoke_gen/nextjs_frontend/out/admin/index.html +1 -0
  82. karaoke_gen/nextjs_frontend/out/admin/index.txt +25 -0
  83. karaoke_gen/nextjs_frontend/out/admin/jobs/__next._full.txt +25 -0
  84. karaoke_gen/nextjs_frontend/out/admin/jobs/__next._head.txt +8 -0
  85. karaoke_gen/nextjs_frontend/out/admin/jobs/__next._index.txt +9 -0
  86. karaoke_gen/nextjs_frontend/out/admin/jobs/__next._tree.txt +2 -0
  87. karaoke_gen/nextjs_frontend/out/admin/jobs/__next.admin.jobs.__PAGE__.txt +9 -0
  88. karaoke_gen/nextjs_frontend/out/admin/jobs/__next.admin.jobs.txt +4 -0
  89. karaoke_gen/nextjs_frontend/out/admin/jobs/__next.admin.txt +7 -0
  90. karaoke_gen/nextjs_frontend/out/admin/jobs/index.html +1 -0
  91. karaoke_gen/nextjs_frontend/out/admin/jobs/index.txt +25 -0
  92. karaoke_gen/nextjs_frontend/out/admin/rate-limits/__next._full.txt +25 -0
  93. karaoke_gen/nextjs_frontend/out/admin/rate-limits/__next._head.txt +8 -0
  94. karaoke_gen/nextjs_frontend/out/admin/rate-limits/__next._index.txt +9 -0
  95. karaoke_gen/nextjs_frontend/out/admin/rate-limits/__next._tree.txt +2 -0
  96. karaoke_gen/nextjs_frontend/out/admin/rate-limits/__next.admin.rate-limits.__PAGE__.txt +9 -0
  97. karaoke_gen/nextjs_frontend/out/admin/rate-limits/__next.admin.rate-limits.txt +4 -0
  98. karaoke_gen/nextjs_frontend/out/admin/rate-limits/__next.admin.txt +7 -0
  99. karaoke_gen/nextjs_frontend/out/admin/rate-limits/index.html +1 -0
  100. karaoke_gen/nextjs_frontend/out/admin/rate-limits/index.txt +25 -0
  101. karaoke_gen/nextjs_frontend/out/admin/searches/__next._full.txt +25 -0
  102. karaoke_gen/nextjs_frontend/out/admin/searches/__next._head.txt +8 -0
  103. karaoke_gen/nextjs_frontend/out/admin/searches/__next._index.txt +9 -0
  104. karaoke_gen/nextjs_frontend/out/admin/searches/__next._tree.txt +2 -0
  105. karaoke_gen/nextjs_frontend/out/admin/searches/__next.admin.searches.__PAGE__.txt +9 -0
  106. karaoke_gen/nextjs_frontend/out/admin/searches/__next.admin.searches.txt +4 -0
  107. karaoke_gen/nextjs_frontend/out/admin/searches/__next.admin.txt +7 -0
  108. karaoke_gen/nextjs_frontend/out/admin/searches/index.html +1 -0
  109. karaoke_gen/nextjs_frontend/out/admin/searches/index.txt +25 -0
  110. karaoke_gen/nextjs_frontend/out/admin/users/__next._full.txt +25 -0
  111. karaoke_gen/nextjs_frontend/out/admin/users/__next._head.txt +8 -0
  112. karaoke_gen/nextjs_frontend/out/admin/users/__next._index.txt +9 -0
  113. karaoke_gen/nextjs_frontend/out/admin/users/__next._tree.txt +2 -0
  114. karaoke_gen/nextjs_frontend/out/admin/users/__next.admin.txt +7 -0
  115. karaoke_gen/nextjs_frontend/out/admin/users/__next.admin.users.__PAGE__.txt +9 -0
  116. karaoke_gen/nextjs_frontend/out/admin/users/__next.admin.users.txt +4 -0
  117. karaoke_gen/nextjs_frontend/out/admin/users/detail/__next._full.txt +25 -0
  118. karaoke_gen/nextjs_frontend/out/admin/users/detail/__next._head.txt +8 -0
  119. karaoke_gen/nextjs_frontend/out/admin/users/detail/__next._index.txt +9 -0
  120. karaoke_gen/nextjs_frontend/out/admin/users/detail/__next._tree.txt +2 -0
  121. karaoke_gen/nextjs_frontend/out/admin/users/detail/__next.admin.txt +7 -0
  122. karaoke_gen/nextjs_frontend/out/admin/users/detail/__next.admin.users.detail.__PAGE__.txt +9 -0
  123. karaoke_gen/nextjs_frontend/out/admin/users/detail/__next.admin.users.detail.txt +4 -0
  124. karaoke_gen/nextjs_frontend/out/admin/users/detail/__next.admin.users.txt +4 -0
  125. karaoke_gen/nextjs_frontend/out/admin/users/detail/index.html +1 -0
  126. karaoke_gen/nextjs_frontend/out/admin/users/detail/index.txt +25 -0
  127. karaoke_gen/nextjs_frontend/out/admin/users/index.html +1 -0
  128. karaoke_gen/nextjs_frontend/out/admin/users/index.txt +25 -0
  129. karaoke_gen/nextjs_frontend/out/app/__next._full.txt +22 -0
  130. karaoke_gen/nextjs_frontend/out/app/__next._head.txt +8 -0
  131. karaoke_gen/nextjs_frontend/out/app/__next._index.txt +9 -0
  132. karaoke_gen/nextjs_frontend/out/app/__next._tree.txt +2 -0
  133. karaoke_gen/nextjs_frontend/out/app/__next.app.__PAGE__.txt +9 -0
  134. karaoke_gen/nextjs_frontend/out/app/__next.app.txt +4 -0
  135. karaoke_gen/nextjs_frontend/out/app/index.html +1 -0
  136. karaoke_gen/nextjs_frontend/out/app/index.txt +22 -0
  137. karaoke_gen/nextjs_frontend/out/app/jobs/__next._full.txt +19 -0
  138. karaoke_gen/nextjs_frontend/out/app/jobs/__next._head.txt +8 -0
  139. karaoke_gen/nextjs_frontend/out/app/jobs/__next._index.txt +9 -0
  140. karaoke_gen/nextjs_frontend/out/app/jobs/__next._tree.txt +2 -0
  141. karaoke_gen/nextjs_frontend/out/app/jobs/__next.app.jobs.$oc$slug.__PAGE__.txt +6 -0
  142. karaoke_gen/nextjs_frontend/out/app/jobs/__next.app.jobs.$oc$slug.txt +4 -0
  143. karaoke_gen/nextjs_frontend/out/app/jobs/__next.app.jobs.txt +4 -0
  144. karaoke_gen/nextjs_frontend/out/app/jobs/__next.app.txt +4 -0
  145. karaoke_gen/nextjs_frontend/out/app/jobs/index.html +1 -0
  146. karaoke_gen/nextjs_frontend/out/app/jobs/index.txt +19 -0
  147. karaoke_gen/nextjs_frontend/out/app/jobs/local/instrumental/__next._full.txt +19 -0
  148. karaoke_gen/nextjs_frontend/out/app/jobs/local/instrumental/__next._head.txt +8 -0
  149. karaoke_gen/nextjs_frontend/out/app/jobs/local/instrumental/__next._index.txt +9 -0
  150. karaoke_gen/nextjs_frontend/out/app/jobs/local/instrumental/__next._tree.txt +2 -0
  151. karaoke_gen/nextjs_frontend/out/app/jobs/local/instrumental/__next.app.jobs.$oc$slug.__PAGE__.txt +6 -0
  152. karaoke_gen/nextjs_frontend/out/app/jobs/local/instrumental/__next.app.jobs.$oc$slug.txt +4 -0
  153. karaoke_gen/nextjs_frontend/out/app/jobs/local/instrumental/__next.app.jobs.txt +4 -0
  154. karaoke_gen/nextjs_frontend/out/app/jobs/local/instrumental/__next.app.txt +4 -0
  155. karaoke_gen/nextjs_frontend/out/app/jobs/local/instrumental/index.html +1 -0
  156. karaoke_gen/nextjs_frontend/out/app/jobs/local/instrumental/index.txt +19 -0
  157. karaoke_gen/nextjs_frontend/out/app/jobs/local/review/__next._full.txt +19 -0
  158. karaoke_gen/nextjs_frontend/out/app/jobs/local/review/__next._head.txt +8 -0
  159. karaoke_gen/nextjs_frontend/out/app/jobs/local/review/__next._index.txt +9 -0
  160. karaoke_gen/nextjs_frontend/out/app/jobs/local/review/__next._tree.txt +2 -0
  161. karaoke_gen/nextjs_frontend/out/app/jobs/local/review/__next.app.jobs.$oc$slug.__PAGE__.txt +6 -0
  162. karaoke_gen/nextjs_frontend/out/app/jobs/local/review/__next.app.jobs.$oc$slug.txt +4 -0
  163. karaoke_gen/nextjs_frontend/out/app/jobs/local/review/__next.app.jobs.txt +4 -0
  164. karaoke_gen/nextjs_frontend/out/app/jobs/local/review/__next.app.txt +4 -0
  165. karaoke_gen/nextjs_frontend/out/app/jobs/local/review/index.html +1 -0
  166. karaoke_gen/nextjs_frontend/out/app/jobs/local/review/index.txt +19 -0
  167. karaoke_gen/nextjs_frontend/out/auth/verify/__next._full.txt +22 -0
  168. karaoke_gen/nextjs_frontend/out/auth/verify/__next._head.txt +8 -0
  169. karaoke_gen/nextjs_frontend/out/auth/verify/__next._index.txt +9 -0
  170. karaoke_gen/nextjs_frontend/out/auth/verify/__next._tree.txt +2 -0
  171. karaoke_gen/nextjs_frontend/out/auth/verify/__next.auth.txt +4 -0
  172. karaoke_gen/nextjs_frontend/out/auth/verify/__next.auth.verify.__PAGE__.txt +9 -0
  173. karaoke_gen/nextjs_frontend/out/auth/verify/__next.auth.verify.txt +4 -0
  174. karaoke_gen/nextjs_frontend/out/auth/verify/index.html +1 -0
  175. karaoke_gen/nextjs_frontend/out/auth/verify/index.txt +22 -0
  176. karaoke_gen/nextjs_frontend/out/index.html +1 -0
  177. karaoke_gen/nextjs_frontend/out/index.txt +22 -0
  178. karaoke_gen/nextjs_frontend/out/manifest.webmanifest +31 -0
  179. karaoke_gen/nextjs_frontend/out/order/success/__next._full.txt +22 -0
  180. karaoke_gen/nextjs_frontend/out/order/success/__next._head.txt +8 -0
  181. karaoke_gen/nextjs_frontend/out/order/success/__next._index.txt +9 -0
  182. karaoke_gen/nextjs_frontend/out/order/success/__next._tree.txt +2 -0
  183. karaoke_gen/nextjs_frontend/out/order/success/__next.order.success.__PAGE__.txt +9 -0
  184. karaoke_gen/nextjs_frontend/out/order/success/__next.order.success.txt +4 -0
  185. karaoke_gen/nextjs_frontend/out/order/success/__next.order.txt +4 -0
  186. karaoke_gen/nextjs_frontend/out/order/success/index.html +1 -0
  187. karaoke_gen/nextjs_frontend/out/order/success/index.txt +22 -0
  188. karaoke_gen/nextjs_frontend/out/payment/success/__next._full.txt +22 -0
  189. karaoke_gen/nextjs_frontend/out/payment/success/__next._head.txt +8 -0
  190. karaoke_gen/nextjs_frontend/out/payment/success/__next._index.txt +9 -0
  191. karaoke_gen/nextjs_frontend/out/payment/success/__next._tree.txt +2 -0
  192. karaoke_gen/nextjs_frontend/out/payment/success/__next.payment.success.__PAGE__.txt +9 -0
  193. karaoke_gen/nextjs_frontend/out/payment/success/__next.payment.success.txt +4 -0
  194. karaoke_gen/nextjs_frontend/out/payment/success/__next.payment.txt +4 -0
  195. karaoke_gen/nextjs_frontend/out/payment/success/index.html +1 -0
  196. karaoke_gen/nextjs_frontend/out/payment/success/index.txt +22 -0
  197. karaoke_gen/nextjs_frontend/out/screenshots/email-action_reminder.png +0 -0
  198. karaoke_gen/nextjs_frontend/out/screenshots/email-beta_welcome.png +0 -0
  199. karaoke_gen/nextjs_frontend/out/screenshots/email-job_completion.png +0 -0
  200. karaoke_gen/nextjs_frontend/out/screenshots/example-output.avif +0 -0
  201. karaoke_gen/nextjs_frontend/out/screenshots/homepage-full.png +0 -0
  202. karaoke_gen/nextjs_frontend/out/screenshots/homepage-hero.png +0 -0
  203. karaoke_gen/nextjs_frontend/out/screenshots/instrumental-review.avif +0 -0
  204. karaoke_gen/nextjs_frontend/out/screenshots/instrumental-review.png +0 -0
  205. karaoke_gen/nextjs_frontend/out/screenshots/job-dashboard.avif +0 -0
  206. karaoke_gen/nextjs_frontend/out/screenshots/lyrics-review.avif +0 -0
  207. karaoke_gen/nextjs_frontend/out/screenshots/lyrics-review.png +0 -0
  208. karaoke_gen/nextjs_frontend/out/sw.js +183 -0
  209. karaoke_gen/utils/cli_args.py +3 -3
  210. karaoke_gen/utils/gen_cli.py +4 -0
  211. karaoke_gen/utils/remote_cli.py +8 -40
  212. {karaoke_gen-0.105.4.dist-info → karaoke_gen-0.107.0.dist-info}/METADATA +1 -1
  213. {karaoke_gen-0.105.4.dist-info → karaoke_gen-0.107.0.dist-info}/RECORD +227 -121
  214. {karaoke_gen-0.105.4.dist-info → karaoke_gen-0.107.0.dist-info}/WHEEL +1 -1
  215. lyrics_transcriber/correction/agentic/agent.py +83 -60
  216. lyrics_transcriber/correction/anchor_sequence.py +48 -3
  217. lyrics_transcriber/correction/corrector.py +92 -58
  218. lyrics_transcriber/review/server.py +165 -33
  219. lyrics_transcriber/utils/tracing.py +214 -0
  220. karaoke_gen/instrumental_review/static/index.html +0 -1721
  221. lyrics_transcriber/frontend/.gitignore +0 -24
  222. lyrics_transcriber/frontend/.yarn/releases/yarn-4.7.0.cjs +0 -935
  223. lyrics_transcriber/frontend/.yarnrc.yml +0 -3
  224. lyrics_transcriber/frontend/README.md +0 -50
  225. lyrics_transcriber/frontend/REPLACE_ALL_FUNCTIONALITY.md +0 -210
  226. lyrics_transcriber/frontend/__init__.py +0 -25
  227. lyrics_transcriber/frontend/e2e/agentic-corrections.spec.ts +0 -207
  228. lyrics_transcriber/frontend/e2e/fixtures/agentic-correction-data.json +0 -226
  229. lyrics_transcriber/frontend/eslint.config.js +0 -28
  230. lyrics_transcriber/frontend/index.html +0 -22
  231. lyrics_transcriber/frontend/package-lock.json +0 -4553
  232. lyrics_transcriber/frontend/package.json +0 -48
  233. lyrics_transcriber/frontend/playwright.config.ts +0 -69
  234. lyrics_transcriber/frontend/public/android-chrome-192x192.png +0 -0
  235. lyrics_transcriber/frontend/public/android-chrome-512x512.png +0 -0
  236. lyrics_transcriber/frontend/src/App.tsx +0 -243
  237. lyrics_transcriber/frontend/src/api.ts +0 -262
  238. lyrics_transcriber/frontend/src/components/AIFeedbackModal.tsx +0 -111
  239. lyrics_transcriber/frontend/src/components/AddLyricsModal.tsx +0 -114
  240. lyrics_transcriber/frontend/src/components/AgenticCorrectionMetrics.tsx +0 -204
  241. lyrics_transcriber/frontend/src/components/AppHeader.tsx +0 -65
  242. lyrics_transcriber/frontend/src/components/AudioPlayer.tsx +0 -180
  243. lyrics_transcriber/frontend/src/components/CorrectedWordWithActions.tsx +0 -175
  244. lyrics_transcriber/frontend/src/components/CorrectionAnnotationModal.tsx +0 -359
  245. lyrics_transcriber/frontend/src/components/CorrectionDetailCard.tsx +0 -281
  246. lyrics_transcriber/frontend/src/components/CorrectionMetrics.tsx +0 -162
  247. lyrics_transcriber/frontend/src/components/DurationTimelineView.tsx +0 -257
  248. lyrics_transcriber/frontend/src/components/EditActionBar.tsx +0 -94
  249. lyrics_transcriber/frontend/src/components/EditModal.tsx +0 -720
  250. lyrics_transcriber/frontend/src/components/EditTimelineSection.tsx +0 -592
  251. lyrics_transcriber/frontend/src/components/EditWordList.tsx +0 -431
  252. lyrics_transcriber/frontend/src/components/FileUpload.tsx +0 -77
  253. lyrics_transcriber/frontend/src/components/FindReplaceModal.tsx +0 -467
  254. lyrics_transcriber/frontend/src/components/Header.tsx +0 -520
  255. lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +0 -1526
  256. lyrics_transcriber/frontend/src/components/LyricsSynchronizer/SyncControls.tsx +0 -216
  257. lyrics_transcriber/frontend/src/components/LyricsSynchronizer/TimelineCanvas.tsx +0 -721
  258. lyrics_transcriber/frontend/src/components/LyricsSynchronizer/UpcomingWordsBar.tsx +0 -80
  259. lyrics_transcriber/frontend/src/components/LyricsSynchronizer/index.tsx +0 -999
  260. lyrics_transcriber/frontend/src/components/MetricsDashboard.tsx +0 -51
  261. lyrics_transcriber/frontend/src/components/ModeSelectionModal.tsx +0 -127
  262. lyrics_transcriber/frontend/src/components/ModeSelector.tsx +0 -67
  263. lyrics_transcriber/frontend/src/components/ModelSelector.tsx +0 -23
  264. lyrics_transcriber/frontend/src/components/PreviewVideoSection.tsx +0 -177
  265. lyrics_transcriber/frontend/src/components/ReferenceView.tsx +0 -268
  266. lyrics_transcriber/frontend/src/components/ReplaceAllLyricsModal.tsx +0 -336
  267. lyrics_transcriber/frontend/src/components/ReviewChangesModal.tsx +0 -354
  268. lyrics_transcriber/frontend/src/components/SegmentDetailsModal.tsx +0 -64
  269. lyrics_transcriber/frontend/src/components/TimelineEditor.tsx +0 -383
  270. lyrics_transcriber/frontend/src/components/TimingOffsetModal.tsx +0 -131
  271. lyrics_transcriber/frontend/src/components/TranscriptionView.tsx +0 -266
  272. lyrics_transcriber/frontend/src/components/WordDivider.tsx +0 -191
  273. lyrics_transcriber/frontend/src/components/shared/components/HighlightedText.tsx +0 -466
  274. lyrics_transcriber/frontend/src/components/shared/components/SourceSelector.tsx +0 -56
  275. lyrics_transcriber/frontend/src/components/shared/components/Word.tsx +0 -89
  276. lyrics_transcriber/frontend/src/components/shared/constants.ts +0 -30
  277. lyrics_transcriber/frontend/src/components/shared/hooks/useWordClick.ts +0 -180
  278. lyrics_transcriber/frontend/src/components/shared/styles.ts +0 -13
  279. lyrics_transcriber/frontend/src/components/shared/types.js +0 -2
  280. lyrics_transcriber/frontend/src/components/shared/types.ts +0 -135
  281. lyrics_transcriber/frontend/src/components/shared/utils/keyboardHandlers.ts +0 -177
  282. lyrics_transcriber/frontend/src/components/shared/utils/localStorage.ts +0 -78
  283. lyrics_transcriber/frontend/src/components/shared/utils/referenceLineCalculator.ts +0 -75
  284. lyrics_transcriber/frontend/src/components/shared/utils/segmentOperations.ts +0 -360
  285. lyrics_transcriber/frontend/src/components/shared/utils/timingUtils.ts +0 -110
  286. lyrics_transcriber/frontend/src/components/shared/utils/wordUtils.ts +0 -22
  287. lyrics_transcriber/frontend/src/hooks/useManualSync.ts +0 -537
  288. lyrics_transcriber/frontend/src/main.tsx +0 -11
  289. lyrics_transcriber/frontend/src/theme.ts +0 -406
  290. lyrics_transcriber/frontend/src/types/global.d.ts +0 -9
  291. lyrics_transcriber/frontend/src/types.js +0 -2
  292. lyrics_transcriber/frontend/src/types.ts +0 -199
  293. lyrics_transcriber/frontend/src/validation.ts +0 -132
  294. lyrics_transcriber/frontend/src/vite-env.d.ts +0 -1
  295. lyrics_transcriber/frontend/tsconfig.app.json +0 -26
  296. lyrics_transcriber/frontend/tsconfig.json +0 -25
  297. lyrics_transcriber/frontend/tsconfig.node.json +0 -23
  298. lyrics_transcriber/frontend/tsconfig.tsbuildinfo +0 -1
  299. lyrics_transcriber/frontend/update_version.js +0 -11
  300. lyrics_transcriber/frontend/vite.config.d.ts +0 -2
  301. lyrics_transcriber/frontend/vite.config.js +0 -15
  302. lyrics_transcriber/frontend/vite.config.ts +0 -16
  303. lyrics_transcriber/frontend/web_assets/android-chrome-192x192.png +0 -0
  304. lyrics_transcriber/frontend/web_assets/android-chrome-512x512.png +0 -0
  305. lyrics_transcriber/frontend/web_assets/apple-touch-icon.png +0 -0
  306. lyrics_transcriber/frontend/web_assets/assets/index-BSMgOq4Z.js +0 -44465
  307. lyrics_transcriber/frontend/web_assets/assets/index-BSMgOq4Z.js.map +0 -1
  308. lyrics_transcriber/frontend/web_assets/favicon-16x16.png +0 -0
  309. lyrics_transcriber/frontend/web_assets/favicon-32x32.png +0 -0
  310. lyrics_transcriber/frontend/web_assets/favicon.ico +0 -0
  311. lyrics_transcriber/frontend/web_assets/index.html +0 -22
  312. lyrics_transcriber/frontend/web_assets/nomad-karaoke-logo.png +0 -0
  313. lyrics_transcriber/frontend/web_assets/nomad-karaoke-logo.svg +0 -5
  314. lyrics_transcriber/frontend/yarn.lock +0 -3711
  315. {lyrics_transcriber/frontend/public → karaoke_gen/nextjs_frontend/out}/apple-touch-icon.png +0 -0
  316. {lyrics_transcriber/frontend/public → karaoke_gen/nextjs_frontend/out}/favicon-16x16.png +0 -0
  317. {lyrics_transcriber/frontend/public → karaoke_gen/nextjs_frontend/out}/favicon-32x32.png +0 -0
  318. {lyrics_transcriber/frontend/public → karaoke_gen/nextjs_frontend/out}/favicon.ico +0 -0
  319. {lyrics_transcriber/frontend/public → karaoke_gen/nextjs_frontend/out}/nomad-karaoke-logo.svg +0 -0
  320. /lyrics_transcriber/frontend/public/nomad-karaoke-logo.png → /karaoke_gen/nextjs_frontend/out/nomad-logo.png +0 -0
  321. {karaoke_gen-0.105.4.dist-info → karaoke_gen-0.107.0.dist-info}/entry_points.txt +0 -0
  322. {karaoke_gen-0.105.4.dist-info → karaoke_gen-0.107.0.dist-info}/licenses/LICENSE +0 -0
@@ -858,8 +858,19 @@ async def enroll_beta_tester(
858
858
  detail="Access denied from this location"
859
859
  )
860
860
 
861
- # 3. Check IP-based enrollment rate limit (1 per 24h per IP)
862
- if ip_address:
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
- # 4. Check for duplicate enrollment via normalized email
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
- import urllib.parse
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
- base_api_url = f"{self.backend_url}/api/jobs/{job_id}"
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/lyrics/" in url
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 test_build_review_url_with_audio_hash(self):
33
- """Test review URL with audio hash parameter."""
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
- url = service._build_review_url("job-123", audio_hash="abc123")
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
- assert "audioHash=abc123" in url
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 test_build_review_url_with_review_token(self):
43
- """Test review URL with review token parameter."""
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
- url = service._build_review_url("job-123", review_token="token456")
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 "reviewToken=token456" in url
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/" in url
88
- assert "baseApiUrl=" in url
57
+ assert url == "https://gen.nomadkaraoke.com/app/jobs/job-123/instrumental"
89
58
 
90
- def test_build_instrumental_url_with_token(self):
91
- """Test instrumental URL with token parameter."""
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
- assert "instrumentalToken=inst-token" in url
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 "audioHash=hash123" in review_url
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 "instrumentalToken=inst-token" in instrumental_url
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