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
@@ -6,7 +6,7 @@ export interface ApiClient {
6
6
  getCorrectionData: () => Promise<CorrectionData>;
7
7
  submitCorrections: (data: CorrectionData) => Promise<void>;
8
8
  getAudioUrl: (audioHash: string) => string;
9
- generatePreviewVideo: (data: CorrectionData) => Promise<PreviewVideoResponse>;
9
+ generatePreviewVideo: (data: CorrectionData, options?: PreviewOptions) => Promise<PreviewVideoResponse>;
10
10
  getPreviewVideoUrl: (previewHash: string) => string;
11
11
  updateHandlers: (enabledHandlers: string[]) => Promise<CorrectionData>;
12
12
  isUpdatingHandlers?: boolean;
@@ -21,6 +21,11 @@ interface CorrectionUpdate {
21
21
  corrected_segments: CorrectionData['corrected_segments'];
22
22
  }
23
23
 
24
+ // Add interface for preview generation options
25
+ export interface PreviewOptions {
26
+ use_background_image?: boolean;
27
+ }
28
+
24
29
  // Add new interface for preview response
25
30
  interface PreviewVideoResponse {
26
31
  status: "success" | "error";
@@ -96,11 +101,13 @@ export class LiveApiClient implements ApiClient {
96
101
  return this.buildUrl(`/audio/${audioHash}`)
97
102
  }
98
103
 
99
- async generatePreviewVideo(data: CorrectionData): Promise<PreviewVideoResponse> {
104
+ async generatePreviewVideo(data: CorrectionData, options?: PreviewOptions): Promise<PreviewVideoResponse> {
100
105
  // Extract only the needed fields, just like in submitCorrections
101
- const updatePayload: CorrectionUpdate = {
106
+ // Include use_background_image option (defaults to false for fast black background)
107
+ const updatePayload = {
102
108
  corrections: data.corrections,
103
- corrected_segments: data.corrected_segments
109
+ corrected_segments: data.corrected_segments,
110
+ use_background_image: options?.use_background_image ?? false,
104
111
  };
105
112
 
106
113
  const response = await fetch(this.buildUrl('/preview-video'), {
@@ -224,7 +231,8 @@ export class FileOnlyClient implements ApiClient {
224
231
  throw new Error('Not supported in file-only mode');
225
232
  }
226
233
 
227
- async generatePreviewVideo(): Promise<PreviewVideoResponse> {
234
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
235
+ async generatePreviewVideo(_data: CorrectionData, _options?: PreviewOptions): Promise<PreviewVideoResponse> {
228
236
  throw new Error('Not supported in file-only mode');
229
237
  }
230
238
 
@@ -1,5 +1,5 @@
1
- import { Box, Typography, CircularProgress, Alert, Button } from '@mui/material'
2
- import { useState, useEffect } from 'react'
1
+ import { Box, Typography, CircularProgress, Alert, Button, FormControlLabel, Checkbox } from '@mui/material'
2
+ import { useState, useEffect, useCallback } from 'react'
3
3
  import { ApiClient } from '../api'
4
4
  import { CorrectionData } from '../types'
5
5
  import { applyOffsetToCorrectionData } from './shared/utils/timingUtils'
@@ -25,75 +25,108 @@ export default function PreviewVideoSection({
25
25
  error?: string;
26
26
  }>({ status: 'loading' });
27
27
 
28
- // Generate preview when modal opens
29
- useEffect(() => {
30
- if (isModalOpen && apiClient) {
31
- const generatePreview = async () => {
32
- setPreviewState({ status: 'loading' });
33
- try {
34
- // Debug logging for timing offset
35
- console.log(`[TIMING] PreviewVideoSection - Current timing offset: ${timingOffsetMs}ms`);
36
-
37
- // Apply timing offset if needed
38
- const dataToPreview = timingOffsetMs !== 0
39
- ? applyOffsetToCorrectionData(updatedData, timingOffsetMs)
40
- : updatedData;
41
-
42
- // Log some example timestamps after potential offset application
43
- if (dataToPreview.corrected_segments.length > 0) {
44
- const firstSegment = dataToPreview.corrected_segments[0];
45
- console.log(`[TIMING] Preview - First segment id: ${firstSegment.id}`);
46
- console.log(`[TIMING] - start_time: ${firstSegment.start_time}, end_time: ${firstSegment.end_time}`);
47
-
48
- if (firstSegment.words.length > 0) {
49
- const firstWord = firstSegment.words[0];
50
- console.log(`[TIMING] - first word "${firstWord.text}" time: ${firstWord.start_time} -> ${firstWord.end_time}`);
51
- }
52
- }
53
-
54
- const response = await apiClient.generatePreviewVideo(dataToPreview);
55
-
56
- if (response.status === 'error') {
57
- setPreviewState({
58
- status: 'error',
59
- error: response.message || 'Failed to generate preview video'
60
- });
61
- return;
62
- }
28
+ // Toggle for rendering with theme background image (slower) vs black background (faster)
29
+ const [useBackgroundImage, setUseBackgroundImage] = useState(false);
63
30
 
64
- if (!response.preview_hash) {
65
- setPreviewState({
66
- status: 'error',
67
- error: 'No preview hash received from server'
68
- });
69
- return;
70
- }
31
+ // Memoized function to generate preview
32
+ const generatePreview = useCallback(async () => {
33
+ if (!apiClient) return;
34
+
35
+ setPreviewState({ status: 'loading' });
36
+ try {
37
+ // Debug logging for timing offset
38
+ console.log(`[TIMING] PreviewVideoSection - Current timing offset: ${timingOffsetMs}ms`);
39
+ console.log(`[PREVIEW] Using background image: ${useBackgroundImage}`);
40
+
41
+ // Apply timing offset if needed
42
+ const dataToPreview = timingOffsetMs !== 0
43
+ ? applyOffsetToCorrectionData(updatedData, timingOffsetMs)
44
+ : updatedData;
71
45
 
72
- const videoUrl = apiClient.getPreviewVideoUrl(response.preview_hash);
73
- setPreviewState({
74
- status: 'ready',
75
- videoUrl
76
- });
77
- } catch (error) {
78
- setPreviewState({
79
- status: 'error',
80
- error: (error as Error).message || 'Failed to generate preview video'
81
- });
46
+ // Log some example timestamps after potential offset application
47
+ if (dataToPreview.corrected_segments.length > 0) {
48
+ const firstSegment = dataToPreview.corrected_segments[0];
49
+ console.log(`[TIMING] Preview - First segment id: ${firstSegment.id}`);
50
+ console.log(`[TIMING] - start_time: ${firstSegment.start_time}, end_time: ${firstSegment.end_time}`);
51
+
52
+ if (firstSegment.words.length > 0) {
53
+ const firstWord = firstSegment.words[0];
54
+ console.log(`[TIMING] - first word "${firstWord.text}" time: ${firstWord.start_time} -> ${firstWord.end_time}`);
82
55
  }
83
- };
56
+ }
57
+
58
+ const response = await apiClient.generatePreviewVideo(dataToPreview, {
59
+ use_background_image: useBackgroundImage
60
+ });
84
61
 
62
+ if (response.status === 'error') {
63
+ setPreviewState({
64
+ status: 'error',
65
+ error: response.message || 'Failed to generate preview video'
66
+ });
67
+ return;
68
+ }
69
+
70
+ if (!response.preview_hash) {
71
+ setPreviewState({
72
+ status: 'error',
73
+ error: 'No preview hash received from server'
74
+ });
75
+ return;
76
+ }
77
+
78
+ const videoUrl = apiClient.getPreviewVideoUrl(response.preview_hash);
79
+ setPreviewState({
80
+ status: 'ready',
81
+ videoUrl
82
+ });
83
+ } catch (error) {
84
+ setPreviewState({
85
+ status: 'error',
86
+ error: (error as Error).message || 'Failed to generate preview video'
87
+ });
88
+ }
89
+ }, [apiClient, updatedData, timingOffsetMs, useBackgroundImage]);
90
+
91
+ // Generate preview when modal opens or when background toggle changes
92
+ useEffect(() => {
93
+ if (isModalOpen && apiClient) {
85
94
  generatePreview();
86
95
  }
87
- }, [isModalOpen, apiClient, updatedData, timingOffsetMs]);
96
+ }, [isModalOpen, apiClient, generatePreview]);
88
97
 
89
98
  if (!apiClient) return null;
90
99
 
91
100
  return (
92
101
  <Box sx={{ mb: 2 }}>
102
+ {/* Background image toggle */}
103
+ <Box sx={{ px: 2, pt: 2, pb: 1 }}>
104
+ <FormControlLabel
105
+ control={
106
+ <Checkbox
107
+ checked={useBackgroundImage}
108
+ onChange={(e) => setUseBackgroundImage(e.target.checked)}
109
+ disabled={previewState.status === 'loading'}
110
+ size="small"
111
+ />
112
+ }
113
+ label={
114
+ <Box>
115
+ <Typography variant="body2" component="span">
116
+ Render with theme background
117
+ </Typography>
118
+ <Typography variant="caption" color="text.secondary" display="block">
119
+ Preview uses black background for speed (~10s). Enable for theme background (~30-60s).
120
+ </Typography>
121
+ </Box>
122
+ }
123
+ />
124
+ </Box>
125
+
93
126
  {previewState.status === 'loading' && (
94
127
  <Box sx={{ display: 'flex', alignItems: 'center', gap: 2, p: 2 }}>
95
128
  <CircularProgress size={24} />
96
- <Typography>Generating preview video...</Typography>
129
+ <Typography>Generating preview video{useBackgroundImage ? ' with theme background' : ''}...</Typography>
97
130
  </Box>
98
131
  )}
99
132