karaoke-gen 0.99.3__py3-none-any.whl → 0.101.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 (42) hide show
  1. backend/api/routes/admin.py +512 -1
  2. backend/api/routes/audio_search.py +13 -2
  3. backend/api/routes/file_upload.py +42 -1
  4. backend/api/routes/internal.py +6 -0
  5. backend/api/routes/jobs.py +9 -1
  6. backend/api/routes/review.py +13 -6
  7. backend/api/routes/tenant.py +120 -0
  8. backend/api/routes/users.py +167 -245
  9. backend/main.py +6 -1
  10. backend/middleware/__init__.py +7 -1
  11. backend/middleware/tenant.py +192 -0
  12. backend/models/job.py +19 -3
  13. backend/models/tenant.py +208 -0
  14. backend/models/user.py +18 -0
  15. backend/services/email_service.py +253 -6
  16. backend/services/firestore_service.py +6 -0
  17. backend/services/job_manager.py +32 -1
  18. backend/services/stripe_service.py +61 -35
  19. backend/services/tenant_service.py +285 -0
  20. backend/services/user_service.py +85 -7
  21. backend/tests/emulator/test_made_for_you_integration.py +167 -0
  22. backend/tests/test_admin_job_files.py +337 -0
  23. backend/tests/test_admin_job_reset.py +384 -0
  24. backend/tests/test_admin_job_update.py +326 -0
  25. backend/tests/test_email_service.py +233 -0
  26. backend/tests/test_impersonation.py +223 -0
  27. backend/tests/test_job_creation_regression.py +4 -0
  28. backend/tests/test_job_manager.py +146 -1
  29. backend/tests/test_made_for_you.py +2086 -0
  30. backend/tests/test_models.py +139 -0
  31. backend/tests/test_tenant_api.py +350 -0
  32. backend/tests/test_tenant_middleware.py +345 -0
  33. backend/tests/test_tenant_models.py +406 -0
  34. backend/tests/test_tenant_service.py +418 -0
  35. backend/workers/video_worker.py +8 -3
  36. {karaoke_gen-0.99.3.dist-info → karaoke_gen-0.101.0.dist-info}/METADATA +1 -1
  37. {karaoke_gen-0.99.3.dist-info → karaoke_gen-0.101.0.dist-info}/RECORD +42 -28
  38. lyrics_transcriber/frontend/src/api.ts +13 -5
  39. lyrics_transcriber/frontend/src/components/PreviewVideoSection.tsx +90 -57
  40. {karaoke_gen-0.99.3.dist-info → karaoke_gen-0.101.0.dist-info}/WHEEL +0 -0
  41. {karaoke_gen-0.99.3.dist-info → karaoke_gen-0.101.0.dist-info}/entry_points.txt +0 -0
  42. {karaoke_gen-0.99.3.dist-info → karaoke_gen-0.101.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,418 @@
1
+ """
2
+ Unit tests for TenantService.
3
+
4
+ Tests config loading from GCS, caching behavior, subdomain resolution,
5
+ and email domain validation.
6
+ """
7
+
8
+ import pytest
9
+ from datetime import datetime, timedelta
10
+ from unittest.mock import MagicMock, patch
11
+
12
+ from backend.services.tenant_service import TenantService, get_tenant_service
13
+ from backend.models.tenant import TenantConfig, TenantPublicConfig
14
+
15
+
16
+ # Sample tenant config data that would come from GCS
17
+ SAMPLE_VOCALSTAR_CONFIG = {
18
+ "id": "vocalstar",
19
+ "name": "Vocal Star",
20
+ "subdomain": "vocalstar.nomadkaraoke.com",
21
+ "is_active": True,
22
+ "branding": {
23
+ "logo_url": "https://example.com/logo.png",
24
+ "logo_height": 50,
25
+ "primary_color": "#ffff00",
26
+ "secondary_color": "#006CF9",
27
+ "site_title": "Vocal Star Karaoke Generator",
28
+ },
29
+ "features": {
30
+ "audio_search": False,
31
+ "file_upload": True,
32
+ "youtube_url": False,
33
+ "youtube_upload": False,
34
+ "dropbox_upload": False,
35
+ "gdrive_upload": False,
36
+ "theme_selection": False,
37
+ },
38
+ "defaults": {
39
+ "theme_id": "vocalstar",
40
+ "locked_theme": "vocalstar",
41
+ "distribution_mode": "download_only",
42
+ },
43
+ "auth": {
44
+ "allowed_email_domains": ["vocal-star.com", "vocalstarmusic.com"],
45
+ "require_email_domain": True,
46
+ "sender_email": "vocalstar@nomadkaraoke.com",
47
+ },
48
+ }
49
+
50
+ SAMPLE_INACTIVE_CONFIG = {
51
+ "id": "inactive",
52
+ "name": "Inactive Tenant",
53
+ "subdomain": "inactive.nomadkaraoke.com",
54
+ "is_active": False,
55
+ "branding": {},
56
+ "features": {},
57
+ "defaults": {},
58
+ "auth": {},
59
+ }
60
+
61
+
62
+ class TestTenantService:
63
+ """Tests for TenantService class."""
64
+
65
+ @pytest.fixture
66
+ def mock_storage(self):
67
+ """Create a mock StorageService."""
68
+ storage = MagicMock()
69
+ storage.file_exists.return_value = False
70
+ storage.download_json.return_value = {}
71
+ storage.generate_signed_url.return_value = "https://signed-url.example.com"
72
+ return storage
73
+
74
+ @pytest.fixture
75
+ def tenant_service(self, mock_storage):
76
+ """Create a TenantService with mock storage."""
77
+ return TenantService(storage=mock_storage)
78
+
79
+ # Tests for get_tenant_config()
80
+ def test_get_tenant_config_success(self, tenant_service, mock_storage):
81
+ """Test successful config loading from GCS."""
82
+ mock_storage.file_exists.return_value = True
83
+ mock_storage.download_json.return_value = SAMPLE_VOCALSTAR_CONFIG
84
+
85
+ config = tenant_service.get_tenant_config("vocalstar")
86
+
87
+ assert config is not None
88
+ assert config.id == "vocalstar"
89
+ assert config.name == "Vocal Star"
90
+ assert config.features.audio_search is False
91
+ mock_storage.download_json.assert_called_once_with("tenants/vocalstar/config.json")
92
+
93
+ def test_get_tenant_config_not_found(self, tenant_service, mock_storage):
94
+ """Test returns None when tenant config doesn't exist."""
95
+ mock_storage.file_exists.return_value = False
96
+
97
+ config = tenant_service.get_tenant_config("nonexistent")
98
+
99
+ assert config is None
100
+ mock_storage.download_json.assert_not_called()
101
+
102
+ def test_get_tenant_config_gcs_error(self, tenant_service, mock_storage):
103
+ """Test handles GCS errors gracefully."""
104
+ mock_storage.file_exists.return_value = True
105
+ mock_storage.download_json.side_effect = Exception("GCS error")
106
+
107
+ config = tenant_service.get_tenant_config("vocalstar")
108
+
109
+ assert config is None
110
+
111
+ # Tests for caching behavior
112
+ def test_config_is_cached(self, tenant_service, mock_storage):
113
+ """Test that configs are cached after first load."""
114
+ mock_storage.file_exists.return_value = True
115
+ mock_storage.download_json.return_value = SAMPLE_VOCALSTAR_CONFIG
116
+
117
+ # First call loads from GCS
118
+ config1 = tenant_service.get_tenant_config("vocalstar")
119
+ # Second call should use cache
120
+ config2 = tenant_service.get_tenant_config("vocalstar")
121
+
122
+ assert config1 is config2
123
+ # download_json should only be called once
124
+ assert mock_storage.download_json.call_count == 1
125
+
126
+ def test_force_refresh_bypasses_cache(self, tenant_service, mock_storage):
127
+ """Test that force_refresh reloads from GCS."""
128
+ mock_storage.file_exists.return_value = True
129
+ mock_storage.download_json.return_value = SAMPLE_VOCALSTAR_CONFIG
130
+
131
+ # First call
132
+ tenant_service.get_tenant_config("vocalstar")
133
+ # Force refresh
134
+ tenant_service.get_tenant_config("vocalstar", force_refresh=True)
135
+
136
+ # download_json should be called twice
137
+ assert mock_storage.download_json.call_count == 2
138
+
139
+ def test_cache_expires_after_ttl(self, tenant_service, mock_storage):
140
+ """Test that cache expires after TTL."""
141
+ mock_storage.file_exists.return_value = True
142
+ mock_storage.download_json.return_value = SAMPLE_VOCALSTAR_CONFIG
143
+
144
+ # Load config
145
+ tenant_service.get_tenant_config("vocalstar")
146
+
147
+ # Simulate cache expiration by manipulating cache time
148
+ tenant_service._cache_times["vocalstar"] = datetime.now() - timedelta(seconds=400)
149
+
150
+ # Next call should reload
151
+ tenant_service.get_tenant_config("vocalstar")
152
+
153
+ assert mock_storage.download_json.call_count == 2
154
+
155
+ # Tests for get_tenant_by_subdomain()
156
+ def test_get_tenant_by_subdomain_cached(self, tenant_service, mock_storage):
157
+ """Test subdomain lookup uses cache."""
158
+ mock_storage.file_exists.return_value = True
159
+ mock_storage.download_json.return_value = SAMPLE_VOCALSTAR_CONFIG
160
+
161
+ # First load by ID to populate cache
162
+ tenant_service.get_tenant_config("vocalstar")
163
+
164
+ # Now lookup by subdomain should use cached mapping
165
+ config = tenant_service.get_tenant_by_subdomain("vocalstar.nomadkaraoke.com")
166
+
167
+ assert config is not None
168
+ assert config.id == "vocalstar"
169
+
170
+ def test_get_tenant_by_subdomain_fallback_resolution(self, tenant_service, mock_storage):
171
+ """Test subdomain resolution when not in cache."""
172
+ mock_storage.file_exists.return_value = True
173
+ mock_storage.download_json.return_value = SAMPLE_VOCALSTAR_CONFIG
174
+
175
+ # Lookup by subdomain without prior cache
176
+ config = tenant_service.get_tenant_by_subdomain("vocalstar.nomadkaraoke.com")
177
+
178
+ assert config is not None
179
+ assert config.id == "vocalstar"
180
+
181
+ def test_get_tenant_by_subdomain_not_found(self, tenant_service, mock_storage):
182
+ """Test returns None for unknown subdomain."""
183
+ mock_storage.file_exists.return_value = False
184
+
185
+ config = tenant_service.get_tenant_by_subdomain("unknown.nomadkaraoke.com")
186
+
187
+ assert config is None
188
+
189
+ def test_get_tenant_by_subdomain_case_insensitive(self, tenant_service, mock_storage):
190
+ """Test subdomain lookup is case insensitive."""
191
+ mock_storage.file_exists.return_value = True
192
+ mock_storage.download_json.return_value = SAMPLE_VOCALSTAR_CONFIG
193
+
194
+ config = tenant_service.get_tenant_by_subdomain("VOCALSTAR.NomadKaraoke.COM")
195
+
196
+ assert config is not None
197
+ assert config.id == "vocalstar"
198
+
199
+ # Tests for _resolve_subdomain_to_tenant_id()
200
+ def test_resolve_subdomain_3_parts(self, tenant_service, mock_storage):
201
+ """Test resolving tenant.nomadkaraoke.com pattern."""
202
+ mock_storage.file_exists.return_value = True
203
+
204
+ tenant_id = tenant_service._resolve_subdomain_to_tenant_id("vocalstar.nomadkaraoke.com")
205
+
206
+ assert tenant_id == "vocalstar"
207
+
208
+ def test_resolve_subdomain_4_parts(self, tenant_service, mock_storage):
209
+ """Test resolving tenant.gen.nomadkaraoke.com pattern."""
210
+ mock_storage.file_exists.return_value = True
211
+
212
+ tenant_id = tenant_service._resolve_subdomain_to_tenant_id("vocalstar.gen.nomadkaraoke.com")
213
+
214
+ assert tenant_id == "vocalstar"
215
+
216
+ def test_resolve_subdomain_too_short(self, tenant_service, mock_storage):
217
+ """Test returns None for subdomains with fewer than 3 parts."""
218
+ tenant_id = tenant_service._resolve_subdomain_to_tenant_id("nomadkaraoke.com")
219
+
220
+ assert tenant_id is None
221
+
222
+ def test_resolve_subdomain_tenant_not_exists(self, tenant_service, mock_storage):
223
+ """Test returns None when tenant config doesn't exist in GCS."""
224
+ mock_storage.file_exists.return_value = False
225
+
226
+ tenant_id = tenant_service._resolve_subdomain_to_tenant_id("nonexistent.nomadkaraoke.com")
227
+
228
+ assert tenant_id is None
229
+
230
+ # Tests for get_public_config()
231
+ def test_get_public_config_success(self, tenant_service, mock_storage):
232
+ """Test getting public config."""
233
+ mock_storage.file_exists.return_value = True
234
+ mock_storage.download_json.return_value = SAMPLE_VOCALSTAR_CONFIG
235
+
236
+ public_config = tenant_service.get_public_config("vocalstar")
237
+
238
+ assert public_config is not None
239
+ assert isinstance(public_config, TenantPublicConfig)
240
+ assert public_config.id == "vocalstar"
241
+
242
+ def test_get_public_config_not_found(self, tenant_service, mock_storage):
243
+ """Test returns None when tenant not found."""
244
+ mock_storage.file_exists.return_value = False
245
+
246
+ public_config = tenant_service.get_public_config("nonexistent")
247
+
248
+ assert public_config is None
249
+
250
+ def test_get_public_config_by_subdomain(self, tenant_service, mock_storage):
251
+ """Test getting public config by subdomain."""
252
+ mock_storage.file_exists.return_value = True
253
+ mock_storage.download_json.return_value = SAMPLE_VOCALSTAR_CONFIG
254
+
255
+ public_config = tenant_service.get_public_config_by_subdomain("vocalstar.nomadkaraoke.com")
256
+
257
+ assert public_config is not None
258
+ assert public_config.id == "vocalstar"
259
+
260
+ # Tests for tenant_exists()
261
+ def test_tenant_exists_true(self, tenant_service, mock_storage):
262
+ """Test returns True when config exists."""
263
+ mock_storage.file_exists.return_value = True
264
+
265
+ assert tenant_service.tenant_exists("vocalstar") is True
266
+ mock_storage.file_exists.assert_called_with("tenants/vocalstar/config.json")
267
+
268
+ def test_tenant_exists_false(self, tenant_service, mock_storage):
269
+ """Test returns False when config doesn't exist."""
270
+ mock_storage.file_exists.return_value = False
271
+
272
+ assert tenant_service.tenant_exists("nonexistent") is False
273
+
274
+ # Tests for is_email_allowed_for_tenant()
275
+ def test_is_email_allowed_for_tenant_valid(self, tenant_service, mock_storage):
276
+ """Test email allowed for valid domain."""
277
+ mock_storage.file_exists.return_value = True
278
+ mock_storage.download_json.return_value = SAMPLE_VOCALSTAR_CONFIG
279
+
280
+ assert tenant_service.is_email_allowed_for_tenant("vocalstar", "user@vocal-star.com") is True
281
+
282
+ def test_is_email_allowed_for_tenant_invalid(self, tenant_service, mock_storage):
283
+ """Test email not allowed for invalid domain."""
284
+ mock_storage.file_exists.return_value = True
285
+ mock_storage.download_json.return_value = SAMPLE_VOCALSTAR_CONFIG
286
+
287
+ assert tenant_service.is_email_allowed_for_tenant("vocalstar", "user@gmail.com") is False
288
+
289
+ def test_is_email_allowed_for_tenant_not_found(self, tenant_service, mock_storage):
290
+ """Test returns False when tenant not found."""
291
+ mock_storage.file_exists.return_value = False
292
+
293
+ assert tenant_service.is_email_allowed_for_tenant("nonexistent", "user@any.com") is False
294
+
295
+ # Tests for get_tenant_sender_email()
296
+ def test_get_tenant_sender_email_custom(self, tenant_service, mock_storage):
297
+ """Test returns custom sender email when configured."""
298
+ mock_storage.file_exists.return_value = True
299
+ mock_storage.download_json.return_value = SAMPLE_VOCALSTAR_CONFIG
300
+
301
+ sender = tenant_service.get_tenant_sender_email("vocalstar")
302
+
303
+ assert sender == "vocalstar@nomadkaraoke.com"
304
+
305
+ def test_get_tenant_sender_email_default(self, tenant_service, mock_storage):
306
+ """Test returns default sender when tenant not found."""
307
+ mock_storage.file_exists.return_value = False
308
+
309
+ sender = tenant_service.get_tenant_sender_email("nonexistent")
310
+
311
+ # Should match DEFAULT_SENDER_EMAIL constant (consistent with EmailService)
312
+ assert sender == "gen@nomadkaraoke.com"
313
+
314
+ # Tests for invalidate_cache()
315
+ def test_invalidate_cache_specific_tenant(self, tenant_service, mock_storage):
316
+ """Test invalidating cache for specific tenant."""
317
+ mock_storage.file_exists.return_value = True
318
+ mock_storage.download_json.return_value = SAMPLE_VOCALSTAR_CONFIG
319
+
320
+ # Load into cache
321
+ tenant_service.get_tenant_config("vocalstar")
322
+ assert "vocalstar" in tenant_service._config_cache
323
+
324
+ # Invalidate
325
+ tenant_service.invalidate_cache("vocalstar")
326
+
327
+ assert "vocalstar" not in tenant_service._config_cache
328
+ assert "vocalstar" not in tenant_service._cache_times
329
+
330
+ def test_invalidate_cache_specific_tenant_clears_subdomain_map(self, tenant_service, mock_storage):
331
+ """Test invalidating specific tenant also clears its subdomain map entry."""
332
+ mock_storage.file_exists.return_value = True
333
+ mock_storage.download_json.return_value = SAMPLE_VOCALSTAR_CONFIG
334
+
335
+ # Load into cache (this also populates subdomain map)
336
+ tenant_service.get_tenant_config("vocalstar")
337
+ assert "vocalstar.nomadkaraoke.com" in tenant_service._subdomain_map
338
+ assert tenant_service._subdomain_map["vocalstar.nomadkaraoke.com"] == "vocalstar"
339
+
340
+ # Invalidate specific tenant
341
+ tenant_service.invalidate_cache("vocalstar")
342
+
343
+ # Subdomain map entry should also be removed
344
+ assert "vocalstar.nomadkaraoke.com" not in tenant_service._subdomain_map
345
+
346
+ def test_invalidate_cache_all(self, tenant_service, mock_storage):
347
+ """Test invalidating all caches."""
348
+ mock_storage.file_exists.return_value = True
349
+ mock_storage.download_json.return_value = SAMPLE_VOCALSTAR_CONFIG
350
+
351
+ # Load into cache
352
+ tenant_service.get_tenant_config("vocalstar")
353
+
354
+ # Invalidate all
355
+ tenant_service.invalidate_cache()
356
+
357
+ assert len(tenant_service._config_cache) == 0
358
+ assert len(tenant_service._cache_times) == 0
359
+ assert len(tenant_service._subdomain_map) == 0
360
+
361
+ # Tests for get_asset_url()
362
+ def test_get_asset_url_success(self, tenant_service, mock_storage):
363
+ """Test getting signed URL for existing asset."""
364
+ mock_storage.file_exists.return_value = True
365
+
366
+ url = tenant_service.get_asset_url("vocalstar", "logo.png")
367
+
368
+ assert url == "https://signed-url.example.com"
369
+ mock_storage.file_exists.assert_called_with("tenants/vocalstar/logo.png")
370
+ mock_storage.generate_signed_url.assert_called_with("tenants/vocalstar/logo.png", expiration_minutes=60)
371
+
372
+ def test_get_asset_url_not_found(self, tenant_service, mock_storage):
373
+ """Test returns None when asset doesn't exist."""
374
+ mock_storage.file_exists.return_value = False
375
+
376
+ url = tenant_service.get_asset_url("vocalstar", "missing.png")
377
+
378
+ assert url is None
379
+ mock_storage.generate_signed_url.assert_not_called()
380
+
381
+
382
+ class TestGetTenantServiceSingleton:
383
+ """Tests for the get_tenant_service() singleton function."""
384
+
385
+ def test_returns_same_instance(self):
386
+ """Test that get_tenant_service returns singleton."""
387
+ # Reset the singleton
388
+ import backend.services.tenant_service as module
389
+ module._tenant_service = None
390
+
391
+ with patch.object(TenantService, '__init__', return_value=None):
392
+ service1 = get_tenant_service()
393
+ service2 = get_tenant_service()
394
+
395
+ assert service1 is service2
396
+
397
+ def test_thread_safe_initialization(self):
398
+ """Test that singleton initialization is thread-safe."""
399
+ import threading
400
+ import backend.services.tenant_service as module
401
+
402
+ # Reset the singleton
403
+ module._tenant_service = None
404
+
405
+ instances = []
406
+
407
+ def get_instance():
408
+ with patch.object(TenantService, '__init__', return_value=None):
409
+ instances.append(get_tenant_service())
410
+
411
+ threads = [threading.Thread(target=get_instance) for _ in range(10)]
412
+ for t in threads:
413
+ t.start()
414
+ for t in threads:
415
+ t.join()
416
+
417
+ # All instances should be the same
418
+ assert all(i is instances[0] for i in instances)
@@ -831,9 +831,12 @@ async def _handle_native_distribution(
831
831
  # Don't fail the job - distribution is optional
832
832
 
833
833
  # Upload to Google Drive using native API
834
+ # Skip if orchestrator already uploaded (gdrive_files already populated)
835
+ # This prevents duplicate uploads when using the orchestrator path
834
836
  gdrive_folder_id = getattr(job, 'gdrive_folder_id', None)
835
-
836
- if gdrive_folder_id:
837
+ existing_gdrive_files = result.get('gdrive_files')
838
+
839
+ if gdrive_folder_id and not existing_gdrive_files:
837
840
  try:
838
841
  from backend.services.gdrive_service import get_gdrive_service
839
842
 
@@ -869,7 +872,9 @@ async def _handle_native_distribution(
869
872
  except Exception as e:
870
873
  job_log.error(f"Native Google Drive upload failed: {e}", exc_info=True)
871
874
  # Don't fail the job - distribution is optional
872
-
875
+ elif existing_gdrive_files:
876
+ job_log.info(f"Skipping Google Drive upload - orchestrator already uploaded {len(existing_gdrive_files)} files")
877
+
873
878
  # Update job state_data with brand code and links
874
879
  if brand_code or result.get('dropbox_link') or result.get('gdrive_files'):
875
880
  try:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: karaoke-gen
3
- Version: 0.99.3
3
+ Version: 0.101.0
4
4
  Summary: Generate karaoke videos with synchronized lyrics. Handles the entire process from downloading audio and lyrics to creating the final video with title screens.
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -7,25 +7,28 @@ backend/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  backend/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  backend/api/dependencies.py,sha256=-61nHBhiihUDSVMQd3VuHLP7uvKrUbm1y-j9RmV6_zc,16871
9
9
  backend/api/routes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- backend/api/routes/admin.py,sha256=r9Q7cOnDJjLnQVQ5ql3S6eUOXiVss9hEanHMz6I0Mp4,30499
11
- backend/api/routes/audio_search.py,sha256=r8VPlyA40DUoFPcvNVM_p4faJsjv_Ta_qsY7PhvAzfw,39678
10
+ backend/api/routes/admin.py,sha256=GI4ANzshBUfAzMdRH3M8cUBnzsCbrL1owRXmIY5HK8A,46506
11
+ backend/api/routes/audio_search.py,sha256=CK2lVaM8RDP7i61XEtLeS0AJDfVJSy0ctnduK-ew-hQ,40129
12
12
  backend/api/routes/auth.py,sha256=G1U2KwS3uqBJpgvg2PIe_mZOWCJKPFhhewrmKbW3Z_s,11531
13
- backend/api/routes/file_upload.py,sha256=GQ_m1ZQxFOxxkrjPY6a-9K3jvBC-Jwo_1mRYjZ6cxic,94156
13
+ backend/api/routes/file_upload.py,sha256=nEl06-ZqkKZNzyNZh0RRtuOBox54XvdEOFkYtnSqpv8,95828
14
14
  backend/api/routes/health.py,sha256=iZlhJpmz4vminUy_qrzkyk_1tgG1gmITz4jEWaCWJnM,14010
15
- backend/api/routes/internal.py,sha256=N_Fv0hYQ9gleYNehjHaZb1OKzM78PXDbcqTmHasll2w,15276
16
- backend/api/routes/jobs.py,sha256=L0Aot-BEgOlNv-_AqEVIkO5Uv4BTP5gjmBvPzjWmlR8,60866
17
- backend/api/routes/review.py,sha256=hEvLUcb07DWcBc6E4fTa91zam3Z_a-x2vIvLP_OKR9o,28256
15
+ backend/api/routes/internal.py,sha256=yqzd5V8xGJQ_vTZL85vKIMwmBXf1MIp1nhder-kQEuA,15683
16
+ backend/api/routes/jobs.py,sha256=7i3uAOL9p4cNk1vJHJIIYCBkcJFaJFl6o3iq92Z9KFo,61218
17
+ backend/api/routes/review.py,sha256=BiaXZs-NXl7AVzGiwBzIhzm60fUslcQagSw4XuUByhU,28842
18
+ backend/api/routes/tenant.py,sha256=sM0WVXWGKJeyBNMbPDnAKOD9-SkJBmH4RkMXVWz4dlM,4038
18
19
  backend/api/routes/themes.py,sha256=_fPZg9N2KN-yyr1YR2vAy8FIm8PqVowboQ4WEpFTYiQ,4721
19
- backend/api/routes/users.py,sha256=9ffodZUGzxjoHXLQdOZ-PLEqK0k-wd4zK4xp_GD2wZk,56781
20
+ backend/api/routes/users.py,sha256=nKJKuGedzSMuJVLMU4-CXNc7GsM5c82JzjFYeZtzgsg,52771
20
21
  backend/config.py,sha256=-HsMaacaKuRftYXUXZJr8GBHPdmacFbrNnFDMk7DHNI,8187
21
- backend/main.py,sha256=xYxdM9GT0vLVPmyXGgoa0_BW1JQMGEX5m7lXm_xYnaw,5666
22
- backend/middleware/__init__.py,sha256=lUSMQqQc4UxPazrE1XmyNNEvTKRk7tddAdFmxbTzz7M,157
22
+ backend/main.py,sha256=yRIdnkv_iIyOiLc2btqomxlflym13A-b3ei3X8gI8Ac,5944
23
+ backend/middleware/__init__.py,sha256=usnVRHqfGW56bWJFqaIXz49pSW91uyciBfwO7ANCpIk,369
23
24
  backend/middleware/audit_logging.py,sha256=oGdgbfH_M_3hIMAGrS5HpRvri2jjLxGFnFuA6IiU9BU,4170
25
+ backend/middleware/tenant.py,sha256=a-HEAhyvMUC4uFF6VptaNOO9O6YP64-ZoUitppl79PQ,6373
24
26
  backend/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
- backend/models/job.py,sha256=Aw5pl6uwJpvnqr61HIKmxMB4h7moX4DqcyRoR46zRzw,24683
27
+ backend/models/job.py,sha256=B1Q12F6448okCOkmLmpBnlwGIKi16TqBjVkRttTmqi0,25663
26
28
  backend/models/requests.py,sha256=tCX7ss4to3rKAR_Wu92CZ8uPqNb2nnmCp1XepD4VR5s,4152
29
+ backend/models/tenant.py,sha256=rhj6n7bsX8ClstoHQKCVcLWALI31l5Tepcf_N8IXDdM,7403
27
30
  backend/models/theme.py,sha256=cfNSHIigoAV4P41Jku6Sa1okHSRhUHjFpl1XBVF3kCY,4628
28
- backend/models/user.py,sha256=5kUqfAMQU26DjCvCgjyoP0f07QbOypAnSgpuGhGcMg0,7377
31
+ backend/models/user.py,sha256=2Dl81Sa8_c0cuPJ3uddsLSOAN40mtv-5aUETtFZZK8c,8022
29
32
  backend/models/worker_log.py,sha256=QDgpdRarm00N1TdFoGhKJxZjP9GmiMjrTJb6msw7pHA,5547
30
33
  backend/pyproject.toml,sha256=LAvEXxQSRI94XQcwpBmuSoEu36341HOu53iigsN6dVs,577
31
34
  backend/quick-check.sh,sha256=PUvH0wXaGzWrgWwwJG5M4Bxxt3vVA4mmdEdL6VepVHY,2382
@@ -39,10 +42,10 @@ backend/services/auth_service.py,sha256=rEpb4O9YSTOQaEoP2XXKXRpppWI5sG10AsueQSVc
39
42
  backend/services/credential_manager.py,sha256=VA--PwvL3gboJ21orQb7MIUiovX8n66FohD6w84exX8,31682
40
43
  backend/services/discord_service.py,sha256=LnEasOgaSRlmebDKsVT2yKCdWNDmskUToP3o2wPmdus,5082
41
44
  backend/services/dropbox_service.py,sha256=R47MUKCqK2BEXXmQxMLG6zkPWw02htFoES9q9xiaYAw,10717
42
- backend/services/email_service.py,sha256=fxwcL-n6ZRGLQ7SHXhxUQK1MkprtgE3GcuL5ulUR-90,44699
45
+ backend/services/email_service.py,sha256=gpufwXhNWmdElpWCmKrTLtm2LB8S-0Y5wcyJ3n1i6Jg,52868
43
46
  backend/services/encoding_interface.py,sha256=Y8hPskDyX9DOyAD557F3-SdHjymaBkGXcoC9E_OW7SE,15825
44
47
  backend/services/encoding_service.py,sha256=XAX4hyL2wIHdpBCTc8yglcjVJboZxRVAjc07lUTxgFg,18025
45
- backend/services/firestore_service.py,sha256=XOuXUNd91eVeyEx82uh-A-WMwcyUa2HVxqTfYnGPw-Q,18512
48
+ backend/services/firestore_service.py,sha256=--Va-2lawugskepxo2p9w3ON2KsMGqacSOutCmU_CDQ,18801
46
49
  backend/services/flacfetch_client.py,sha256=zj4VToKamR8bShXZifLvjDmUVa1HHvDhZuaw_b7g4lU,19901
47
50
  backend/services/gce_encoding/README.md,sha256=frTFrp7-ugFAvUd_K9EgE4ud8Xu4Qf1V7BNirQn6qJk,2515
48
51
  backend/services/gce_encoding/__init__.py,sha256=d2H6zM207NSc5afnfgkd3kwOXUu2gFBYjxBSS7tvX_s,571
@@ -50,7 +53,7 @@ backend/services/gce_encoding/main.py,sha256=2A0Bd3zYzbV66ikShB3avpqfNoDFBxr8Ja6
50
53
  backend/services/gce_encoding/requirements.txt,sha256=-hW5ot-PnVoKkxrpY3yV6l68wx7AvzgnLYAWEgDGWNc,408
51
54
  backend/services/gdrive_service.py,sha256=3JTXmPBuxpKopAbWDrh6YhqR5L9BLop1R7caXBnIk2A,13266
52
55
  backend/services/job_logging.py,sha256=theCkAlwMbqyqAbu7vCCRpVRscaIpkam3UrCs_4Yi1g,8052
53
- backend/services/job_manager.py,sha256=y7TCwJal7rPJm4QRuOEviv_SE9RvDiNGHz33MSgGY3M,31701
56
+ backend/services/job_manager.py,sha256=2vBp-a1r3kwOg-CvXRjjlgkBh9U5AlI6yLeCBAjKLMY,33214
54
57
  backend/services/job_notification_service.py,sha256=ywGJUMUDqV23OSDsugOUufpXO6N_24bM9YVaPp8bOk4,9557
55
58
  backend/services/langfuse_preloader.py,sha256=t-Rt_ThjZm4K8W3jlyLn2H3cEy7KNUlhrvOPmdg-gYM,3100
56
59
  backend/services/local_encoding_service.py,sha256=Q6yK2-QL9tHM_pUBoxdyfT7ln2L5xMx82JB2nC5OfNQ,20680
@@ -62,12 +65,13 @@ backend/services/packaging_service.py,sha256=MUuBk0jtG3vGQhuOWbDv9u1uIMwbBC4NknL
62
65
  backend/services/rclone_service.py,sha256=Q6wRAjlBiMMh9FBbbFRp4SHK1ljOQBed9zUJaJgAuhU,3646
63
66
  backend/services/spacy_preloader.py,sha256=rpsMZB0CQWcoXjq8nyZCwJBkyjGheKaCFrfhxCOln5w,1836
64
67
  backend/services/storage_service.py,sha256=gA7Snz1pihzYpRKZjEZx3eDwyMUBezl3ccLuBdpTwE4,8573
65
- backend/services/stripe_service.py,sha256=154TbTuysOiuUFKEmIG3RFZYKYEYhLJR2Weyb4xAhxs,13243
68
+ backend/services/stripe_service.py,sha256=YPBVE2_ZeBYWklcFCTGoRWOuO1UTI16_Un2NKAE-sqE,14825
66
69
  backend/services/structured_logging.py,sha256=daGgZUdc1xIiOdxL9-YtWHDy51hwZqd8ELpomkh2QH4,9063
67
70
  backend/services/template_service.py,sha256=5CdSxBGhdBtxIUz24AhvltUfSWcFqZzBIxb9MjlN4YU,11212
71
+ backend/services/tenant_service.py,sha256=AlG5acJww-rpxGEQbw7eOWe1KaHLOcCn3fM95qDZMGw,9709
68
72
  backend/services/theme_service.py,sha256=6lJqMpFjmRY09CMJcW9IJ6DVPffLmwE0DnpXDUKaJ0U,17374
69
73
  backend/services/tracing.py,sha256=MNpJNMmSOw-9TRlb3dJk7pA8JTmSjAFHj9bSuyM5cOY,17210
70
- backend/services/user_service.py,sha256=TSVuIfM2rF4Acw1ic5TQ-QIjgeKU-Q8r93-PQdd4cwA,26215
74
+ backend/services/user_service.py,sha256=0_ztVqXKNH1jqKiZwqgStEfW95YowJbvYOL-eaBw2oY,29339
71
75
  backend/services/worker_service.py,sha256=WRgh3kconWYmvraEIsx6eUto2frKacauKApFU1wIEiQ,21093
72
76
  backend/services/youtube_service.py,sha256=L2jqcWkw6qIaKh4wEfX894mHaTU0m5qtgIX6TPi6uiQ,3828
73
77
  backend/services/youtube_upload_service.py,sha256=fvo4WxLQaKY_I75ViBMLk6vm87ym2ABHK0Fn_StYN6g,16694
@@ -77,12 +81,16 @@ backend/tests/emulator/__init__.py,sha256=yOGPU5e0JPNYyUpy-u13jW4gEfWQs3dCJxfinc
77
81
  backend/tests/emulator/conftest.py,sha256=VXhoJ3NZg3uiLzSAsushQtozFS7Lt6lmjk7ym-k6V8o,3844
78
82
  backend/tests/emulator/test_e2e_cli_backend.py,sha256=BTrWDv4sRTeOlheZ-d4R37tZsvJ2MhdFLbDb9tjbF08,41018
79
83
  backend/tests/emulator/test_emulator_integration.py,sha256=pm_JzgA2bmjcnHmGA-mZfJhUAYwGx10jv48A2deujRs,11822
84
+ backend/tests/emulator/test_made_for_you_integration.py,sha256=EYWZrYrQdoYk4Eb8G4M7Cyzq5ncAWqeYLuG8a31EAqc,5470
80
85
  backend/tests/emulator/test_style_loading_direct.py,sha256=e-FzlCVW_-ealM9hTGa2qgpwoVRiYKrcXDWB1FjkwXc,16842
81
86
  backend/tests/emulator/test_worker_logs_direct.py,sha256=96EeaSxeNQBtDiZ9ky1-CGE6Lv6ZV3dKuoF3inHxO8A,8158
82
87
  backend/tests/emulator/test_worker_logs_subcollection.py,sha256=whwUXW_-fF-nQp2Z791PNxi0nnvlRwLo87QacF-yMUI,16834
83
88
  backend/tests/requirements-test.txt,sha256=1ixGqdsXFG0hbqUPWUjNGPlKdQgYxo1GajXOiLhs66M,193
84
89
  backend/tests/requirements.txt,sha256=1526vDy39B1ZjJ5URZtoiDGnjvwuxiD9dNrjOLd6-gA,95
85
90
  backend/tests/test_admin_email_endpoints.py,sha256=C1mOX3K4P-_QqSWa8ZSH4gtMnUJ7hqq5rOi00zSKR8g,16592
91
+ backend/tests/test_admin_job_files.py,sha256=LXeV08a4XuaipM-QFFVJKE02e83Muj-JrjtCPlgExuM,13348
92
+ backend/tests/test_admin_job_reset.py,sha256=PsM6cBPeN5d6HO4pejaOQNyTh7OHTOFNPMTPuulcZCI,14292
93
+ backend/tests/test_admin_job_update.py,sha256=19NFazBk-fU8vrKKOoJV9c7pAvskNcAhihpmdQFZNIk,12157
86
94
  backend/tests/test_api_integration.py,sha256=ps4dFoW69NCKJAWvPSr410fEILPxfQNME_YmLPdNqxo,14886
87
95
  backend/tests/test_api_routes.py,sha256=j6alBwXgtKspXxbOiDuNCCVMENk6yxdFJkakbife-rM,3193
88
96
  backend/tests/test_audio_analysis_service.py,sha256=bKIAgyxfdbUOr7atHH-WCbZcZFkWGmGOOn7IfvRF2ms,11373
@@ -96,23 +104,25 @@ backend/tests/test_dependencies.py,sha256=kWiOQEMQWFPIbAM8FEMYI921n3dLOQFJ7HXyRH
96
104
  backend/tests/test_discord_service.py,sha256=OU5mVzjukFtJd_H5e_HxJqSsf78B0TrNfjxhwypbV34,8902
97
105
  backend/tests/test_distribution_services.py,sha256=8oB2vMq5T_4rvOs6BURoLLTQiDf0kUJ-jF8dzJzDYRk,33699
98
106
  backend/tests/test_dropbox_service.py,sha256=nDN1NjiCMS_f1bICWfe3maqbKT5FbhNuKCb1zjyjKq0,19198
99
- backend/tests/test_email_service.py,sha256=jj4EQwbJ2iOiL_-jFR-2t9YJv7KnGP99HcFjZHAq2-I,16754
107
+ backend/tests/test_email_service.py,sha256=u1VSTBCZl7m3GYR-vKV84tu3q7zhMuIhNA5a4OiCDlE,25290
100
108
  backend/tests/test_emulator_integration.py,sha256=m4teGnKLBKQNe9UdFi0-0iR_unnotWs07VWgSd_atuw,10387
101
109
  backend/tests/test_encoding_interface.py,sha256=7q9mKX4QG8-bfkxQgZkmeirK_ynfL88UTbgWS0f097U,14609
102
110
  backend/tests/test_file_upload.py,sha256=mWEIVj6cK6ZzKf4X0D0JhSk6pmJeCO0dOawV2GED7tc,68038
103
111
  backend/tests/test_flacfetch_client.py,sha256=ZquMDLFVgORr9GoNfybOV9Z3eruborRgCvBRY6l-V8A,22614
104
112
  backend/tests/test_gdrive_service.py,sha256=fh02KPaFqLEWfPaDQvSehn6r6RLq0bgt6MdXenF1kxA,19762
113
+ backend/tests/test_impersonation.py,sha256=vuCJaYmPOObLvFWuzxRvqadeRtFXgsbQxyOKd4TaxHg,9087
105
114
  backend/tests/test_instrumental_api.py,sha256=2eWaoEAYVY02HZgY4esn5echuWGv_sznoO6FYWIgTbQ,17210
106
115
  backend/tests/test_internal_api.py,sha256=qMGSDCkYd5OY_Z85vQu10xjqJn1JtaQcaKSnSBpVijw,12980
107
- backend/tests/test_job_creation_regression.py,sha256=C1KgyNhpJzchl10QK8trfmACs2BrEwYd-WK1YMMS-7I,22602
108
- backend/tests/test_job_manager.py,sha256=Y5De6L0BvK4yk6D4w-pDBKBk2R7w89SfSyUz5krgphE,13187
116
+ backend/tests/test_job_creation_regression.py,sha256=ydd97391bMq40MSTlTlvMn3_zjtFDWzSQLjYczoqR_U,22826
117
+ backend/tests/test_job_manager.py,sha256=9SvwVkgN5VoyaJH-vzVN3VRoXpMJf4YAluLmspZrhzs,18862
109
118
  backend/tests/test_job_manager_notifications.py,sha256=WlzXXxTCmHHCrD4XWu2j2fDYODlHGKThOQpZQeyhPXM,12996
110
119
  backend/tests/test_job_notification_service.py,sha256=YbXqCWbsK8O5WICItb3VCrEY8qKqoJ3NBEZtwCs7AYM,17892
111
120
  backend/tests/test_jobs_api.py,sha256=z3pGTawTpt75a1bQ5x4ynw3pTlNihLA6vqktTc6mBAU,11092
112
121
  backend/tests/test_local_encoding_service.py,sha256=TbBaFOB-8Re09G-qXIQCl8mS9WlQMFNQF01V64IekyI,14638
113
122
  backend/tests/test_local_preview_encoding_service.py,sha256=VwxpcAjDdYaVauSsrJ609U575VWRZbLsXew8BqBXpJY,20228
123
+ backend/tests/test_made_for_you.py,sha256=WNiJhmv_zse96Yg4scEXnpu66MMc0Hi4pNyiadfdlQ8,87628
114
124
  backend/tests/test_main.py,sha256=BvzcadLUV0SEUO1ViKm0djgy_TEKXm8PPvgfcCCEJAY,3348
115
- backend/tests/test_models.py,sha256=3qmuI-GmOXzhPTSLO2jlAl9E1hqtT057SkH4t4Dbchk,34435
125
+ backend/tests/test_models.py,sha256=buUjtm3TaiIff58SCpG0Du1Bnm-S9X8LyaW64zEhb4U,39606
116
126
  backend/tests/test_packaging_service.py,sha256=v4Bj8R61Fx0Kng6RoTj3SRN9jmxow5qCVtTJstTWNfE,13916
117
127
  backend/tests/test_requests.py,sha256=DI2IG67hHk2rhuzUfz_QNJCLRTy1NZPLsnSdZbIfPA8,6817
118
128
  backend/tests/test_routes_jobs.py,sha256=iqOZ93ACKpizSzLEJUx5hIo0vkKe2oIT7s7iZqQUJaE,11258
@@ -123,6 +133,10 @@ backend/tests/test_spacy_preloader.py,sha256=uWpkrOPUOOcW1-fWClKMKVhXHGk30FXq537
123
133
  backend/tests/test_storage_service.py,sha256=gorn3aFwKSr8lihxcG7kza7fYvDsTvxJhOxzuTtPK_o,19069
124
134
  backend/tests/test_style_upload.py,sha256=8G2dGgvyMGXsh1jk8PcGpTlSdBQAlHn13kpZcKn6rTY,10491
125
135
  backend/tests/test_template_service.py,sha256=BKURQ1M-hXXgdBl9LqhPQKfpItWOYDRBSt1y5FXQzaM,10431
136
+ backend/tests/test_tenant_api.py,sha256=P4395WYGldJIX7hYcbk96bIDW0Hogcj2McUG6iApNmc,13441
137
+ backend/tests/test_tenant_middleware.py,sha256=0WflieACxT1c_k-C2-yQ5sap_xm6z6w3p3ys09B8iMA,12577
138
+ backend/tests/test_tenant_models.py,sha256=NwNz197f5IPuhNU8f0d2OGBTeE7y7K2oulHfOJRo1EQ,15228
139
+ backend/tests/test_tenant_service.py,sha256=39bL1B0poa1LyxHDwFdQgSVu-QJ96rpe7Occ_ItC1Zc,16494
126
140
  backend/tests/test_theme_service.py,sha256=I_BrXjDAwmqGhxClJA8kE9FDYdQmtdYQINjIwyI3NTs,20454
127
141
  backend/tests/test_unicode_sanitization.py,sha256=M4QfJmHGZ8WrhTfg7QPljPj2kUkN5QUNThtofzZhQiE,22930
128
142
  backend/tests/test_upload_api.py,sha256=oWXPunON3pSTEnE3oE9vIQk63GC6lKM5RgTmaKKQths,10127
@@ -144,7 +158,7 @@ backend/workers/lyrics_worker.py,sha256=nsy90pb0BPAl_lbZ0E7d7jIclcXK7W8d9RxxEdJa
144
158
  backend/workers/render_video_worker.py,sha256=L6-VCxSBY6MbXBBm0-wu_DXxcIWKfW6ynTv9K03WCjY,25163
145
159
  backend/workers/screens_worker.py,sha256=vCS7j4dQRQ_tsWNhz-XH5nau7kGyUSzSp-dzyZpeJEo,21402
146
160
  backend/workers/style_helper.py,sha256=7-PM79dAUAZHTj-bnATy43ImqNdl_00E9pm94CYg1BM,6906
147
- backend/workers/video_worker.py,sha256=49Q7D3bNg38iJk26q2k8y3v-oUscUtE50qaUymlFQbk,55292
161
+ backend/workers/video_worker.py,sha256=5r3PcYdoca16fjZtFzCwfKm-G7QjL98ZLMhw6qGVDNE,55670
148
162
  backend/workers/video_worker_orchestrator.py,sha256=NInZjFwztqcYneuxrkDqEFWV7z5BzGlaaoJxj812KA4,27510
149
163
  backend/workers/worker_logging.py,sha256=nlbGsjDjdkv28Fm-95vKBumy6P47dGiNN5Zf3r0A480,9928
150
164
  karaoke_gen/__init__.py,sha256=wHpDbURJxmJAMNZ0uQjISv5MIT7KD9RWYi15xlYgEhU,1351
@@ -287,7 +301,7 @@ lyrics_transcriber/frontend/public/favicon.ico,sha256=ZK7QvdBuZp0QxPkluCW4IKxfle
287
301
  lyrics_transcriber/frontend/public/nomad-karaoke-logo.png,sha256=jTTBFXV6hGJGolZYQ-dIjgQQbMsehk5XGtsllhLrdzg,212641
288
302
  lyrics_transcriber/frontend/public/nomad-karaoke-logo.svg,sha256=0LOH346_a-1JeYquMWd1v2A3XMN8zLDBeujfWAOeNYk,9185
289
303
  lyrics_transcriber/frontend/src/App.tsx,sha256=STVmqN3xtXambV_5X4M0MNuwYjARHBQfn1cuCqxNGkw,7979
290
- lyrics_transcriber/frontend/src/api.ts,sha256=GcjbOrlU7EdUpZ7MUPFqE1rtH-ckdw8wHtgyQxWateY,8648
304
+ lyrics_transcriber/frontend/src/api.ts,sha256=MDTKq1rZUXKdRHb1YFDKaGuFOK-LO2wC3yvladlWjeY,9085
291
305
  lyrics_transcriber/frontend/src/components/AIFeedbackModal.tsx,sha256=YvJlBP-3udqrOmvwKuMR7FxfIozZq5pVfTYvmhzbnHo,5602
292
306
  lyrics_transcriber/frontend/src/components/AddLyricsModal.tsx,sha256=ubJwQewryjUrXwpBkITQNu4POhoUtDbNA93cqa-yJKY,3416
293
307
  lyrics_transcriber/frontend/src/components/AgenticCorrectionMetrics.tsx,sha256=Yg6FG0LtrneRfAYeBu3crt_RdN-_o7FojtYhDMDKi0o,8595
@@ -314,7 +328,7 @@ lyrics_transcriber/frontend/src/components/MetricsDashboard.tsx,sha256=33XpyHj0s
314
328
  lyrics_transcriber/frontend/src/components/ModeSelectionModal.tsx,sha256=eihGI49r9tKq-AaEtnmVrbiBOoJApWvabaZW4ydmg-4,5302
315
329
  lyrics_transcriber/frontend/src/components/ModeSelector.tsx,sha256=HnBAK_gFgNBJLtMC_ESMVdUapDjmqmoLX8pQeyHfpOw,2651
316
330
  lyrics_transcriber/frontend/src/components/ModelSelector.tsx,sha256=lfG_B5VAzSfrU0FqJl8XptN6DVt2kSljU96HMXo8mf4,559
317
- lyrics_transcriber/frontend/src/components/PreviewVideoSection.tsx,sha256=59ZhG5XsxUZ_dkK8BjTQhYmYP5Wv86uRR-xtuwFRK8c,5582
331
+ lyrics_transcriber/frontend/src/components/PreviewVideoSection.tsx,sha256=zKR_S1AyYAEmr1hnr2IN14mw1igpYbJB12HjaUrXbH4,6803
318
332
  lyrics_transcriber/frontend/src/components/ReferenceView.tsx,sha256=a3CFpbJ8M-kFV3K79xyuo-sfOoC9He_IuXwhIcAOImo,10510
319
333
  lyrics_transcriber/frontend/src/components/ReplaceAllLyricsModal.tsx,sha256=pVlqHrSloxXZV_Ib8cbk1invF7WA3uge5b7pnFPe9Pc,12290
320
334
  lyrics_transcriber/frontend/src/components/ReviewChangesModal.tsx,sha256=VQg_gBFViAxQu9Z75o6rOsvmH5DZBjKq9FkU8aB_7mI,13790
@@ -436,8 +450,8 @@ lyrics_transcriber/transcribers/whisper.py,sha256=YcCB1ic9H6zL1GS0jD0emu8-qlcH0Q
436
450
  lyrics_transcriber/types.py,sha256=UJjaxhVd2o14AG4G8ToU598p0JeYdiTFjpG38jGCoYQ,27917
437
451
  lyrics_transcriber/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
438
452
  lyrics_transcriber/utils/word_utils.py,sha256=-cMGpj9UV4F6IsoDKAV2i1aiqSO8eI91HMAm_igtVMk,958
439
- karaoke_gen-0.99.3.dist-info/METADATA,sha256=unsN1sCERd0RAJ-G2KFKw1UI_1sUpM-ZG6Sl3FODVJ8,23115
440
- karaoke_gen-0.99.3.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
441
- karaoke_gen-0.99.3.dist-info/entry_points.txt,sha256=xIyLe7K84ZyjO8L0_AmNectz93QjGSs5AkApMtlAd4g,160
442
- karaoke_gen-0.99.3.dist-info/licenses/LICENSE,sha256=81R_4XwMZDODHD7JcZeUR8IiCU8AD7Ajl6bmwR9tYDk,1074
443
- karaoke_gen-0.99.3.dist-info/RECORD,,
453
+ karaoke_gen-0.101.0.dist-info/METADATA,sha256=zkFeEtygnskVvZLwCarJ9blhCd4pA60LV75aSZm8RaI,23116
454
+ karaoke_gen-0.101.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
455
+ karaoke_gen-0.101.0.dist-info/entry_points.txt,sha256=xIyLe7K84ZyjO8L0_AmNectz93QjGSs5AkApMtlAd4g,160
456
+ karaoke_gen-0.101.0.dist-info/licenses/LICENSE,sha256=81R_4XwMZDODHD7JcZeUR8IiCU8AD7Ajl6bmwR9tYDk,1074
457
+ karaoke_gen-0.101.0.dist-info/RECORD,,