lyrics-transcriber 0.43.0__py3-none-any.whl → 0.44.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.
- lyrics_transcriber/core/controller.py +58 -24
- lyrics_transcriber/correction/anchor_sequence.py +22 -8
- lyrics_transcriber/correction/corrector.py +47 -3
- lyrics_transcriber/correction/handlers/llm.py +15 -12
- lyrics_transcriber/correction/handlers/llm_providers.py +60 -0
- lyrics_transcriber/frontend/.yarn/install-state.gz +0 -0
- lyrics_transcriber/frontend/dist/assets/{index-D0Gr3Ep7.js → index-DVoI6Z16.js} +10799 -7490
- lyrics_transcriber/frontend/dist/assets/index-DVoI6Z16.js.map +1 -0
- lyrics_transcriber/frontend/dist/index.html +1 -1
- lyrics_transcriber/frontend/src/App.tsx +4 -4
- lyrics_transcriber/frontend/src/api.ts +37 -0
- lyrics_transcriber/frontend/src/components/AddLyricsModal.tsx +114 -0
- lyrics_transcriber/frontend/src/components/AudioPlayer.tsx +14 -10
- lyrics_transcriber/frontend/src/components/CorrectionMetrics.tsx +62 -56
- lyrics_transcriber/frontend/src/components/EditModal.tsx +232 -237
- lyrics_transcriber/frontend/src/components/FindReplaceModal.tsx +467 -0
- lyrics_transcriber/frontend/src/components/GlobalSyncEditor.tsx +675 -0
- lyrics_transcriber/frontend/src/components/Header.tsx +141 -101
- lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +146 -80
- lyrics_transcriber/frontend/src/components/ModeSelector.tsx +22 -13
- lyrics_transcriber/frontend/src/components/PreviewVideoSection.tsx +1 -0
- lyrics_transcriber/frontend/src/components/ReferenceView.tsx +29 -12
- lyrics_transcriber/frontend/src/components/ReviewChangesModal.tsx +21 -4
- lyrics_transcriber/frontend/src/components/TimelineEditor.tsx +29 -15
- lyrics_transcriber/frontend/src/components/TranscriptionView.tsx +34 -16
- lyrics_transcriber/frontend/src/components/WordDivider.tsx +186 -0
- lyrics_transcriber/frontend/src/components/shared/components/HighlightedText.tsx +89 -41
- lyrics_transcriber/frontend/src/components/shared/components/SourceSelector.tsx +9 -2
- lyrics_transcriber/frontend/src/components/shared/components/Word.tsx +28 -3
- lyrics_transcriber/frontend/src/components/shared/types.ts +17 -2
- lyrics_transcriber/frontend/src/components/shared/utils/keyboardHandlers.ts +63 -14
- lyrics_transcriber/frontend/src/components/shared/utils/segmentOperations.ts +192 -0
- lyrics_transcriber/frontend/src/hooks/useManualSync.ts +267 -0
- lyrics_transcriber/frontend/src/main.tsx +7 -1
- lyrics_transcriber/frontend/src/theme.ts +177 -0
- lyrics_transcriber/frontend/src/types.ts +1 -1
- lyrics_transcriber/frontend/tsconfig.tsbuildinfo +1 -1
- lyrics_transcriber/lyrics/base_lyrics_provider.py +2 -2
- lyrics_transcriber/lyrics/user_input_provider.py +44 -0
- lyrics_transcriber/output/generator.py +40 -12
- lyrics_transcriber/output/video.py +18 -8
- lyrics_transcriber/review/server.py +238 -8
- {lyrics_transcriber-0.43.0.dist-info → lyrics_transcriber-0.44.0.dist-info}/METADATA +3 -2
- {lyrics_transcriber-0.43.0.dist-info → lyrics_transcriber-0.44.0.dist-info}/RECORD +47 -41
- lyrics_transcriber/frontend/dist/assets/index-D0Gr3Ep7.js.map +0 -1
- lyrics_transcriber/frontend/src/components/DetailsModal.tsx +0 -252
- lyrics_transcriber/frontend/src/components/WordEditControls.tsx +0 -110
- {lyrics_transcriber-0.43.0.dist-info → lyrics_transcriber-0.44.0.dist-info}/LICENSE +0 -0
- {lyrics_transcriber-0.43.0.dist-info → lyrics_transcriber-0.44.0.dist-info}/WHEEL +0 -0
- {lyrics_transcriber-0.43.0.dist-info → lyrics_transcriber-0.44.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,177 @@
|
|
1
|
+
import { createTheme } from '@mui/material/styles';
|
2
|
+
|
3
|
+
// Create a theme with smaller typography and spacing
|
4
|
+
const theme = createTheme({
|
5
|
+
typography: {
|
6
|
+
// Scale down all typography by about 20%
|
7
|
+
fontSize: 14, // Default is 16
|
8
|
+
h1: {
|
9
|
+
fontSize: '2.5rem', // Default is ~3rem
|
10
|
+
},
|
11
|
+
h2: {
|
12
|
+
fontSize: '2rem', // Default is ~2.5rem
|
13
|
+
},
|
14
|
+
h3: {
|
15
|
+
fontSize: '1.5rem', // Default is ~1.75rem
|
16
|
+
},
|
17
|
+
h4: {
|
18
|
+
fontSize: '1.2rem', // Default is ~1.5rem
|
19
|
+
marginBottom: '0.5rem',
|
20
|
+
},
|
21
|
+
h5: {
|
22
|
+
fontSize: '1rem', // Default is ~1.25rem
|
23
|
+
},
|
24
|
+
h6: {
|
25
|
+
fontSize: '0.9rem', // Default is ~1.1rem
|
26
|
+
marginBottom: '0.5rem',
|
27
|
+
},
|
28
|
+
body1: {
|
29
|
+
fontSize: '0.85rem', // Default is ~1rem
|
30
|
+
},
|
31
|
+
body2: {
|
32
|
+
fontSize: '0.75rem', // Default is ~0.875rem
|
33
|
+
},
|
34
|
+
button: {
|
35
|
+
fontSize: '0.8rem', // Default is ~0.875rem
|
36
|
+
},
|
37
|
+
caption: {
|
38
|
+
fontSize: '0.7rem', // Default is ~0.75rem
|
39
|
+
},
|
40
|
+
},
|
41
|
+
components: {
|
42
|
+
MuiButton: {
|
43
|
+
styleOverrides: {
|
44
|
+
root: {
|
45
|
+
padding: '3px 10px', // Further reduced from 4px 12px
|
46
|
+
minHeight: '30px', // Further reduced from 32px
|
47
|
+
},
|
48
|
+
sizeSmall: {
|
49
|
+
padding: '1px 6px', // Further reduced from 2px 8px
|
50
|
+
minHeight: '24px', // Further reduced from 28px
|
51
|
+
},
|
52
|
+
},
|
53
|
+
},
|
54
|
+
MuiIconButton: {
|
55
|
+
styleOverrides: {
|
56
|
+
root: {
|
57
|
+
padding: '4px', // Further reduced from 6px
|
58
|
+
},
|
59
|
+
sizeSmall: {
|
60
|
+
padding: '2px', // Further reduced from 4px
|
61
|
+
},
|
62
|
+
},
|
63
|
+
},
|
64
|
+
MuiTextField: {
|
65
|
+
styleOverrides: {
|
66
|
+
root: {
|
67
|
+
'& .MuiInputBase-root': {
|
68
|
+
minHeight: '32px', // Further reduced from 36px
|
69
|
+
},
|
70
|
+
},
|
71
|
+
},
|
72
|
+
},
|
73
|
+
MuiDialog: {
|
74
|
+
styleOverrides: {
|
75
|
+
paper: {
|
76
|
+
padding: '8px', // Further reduced from 12px
|
77
|
+
},
|
78
|
+
},
|
79
|
+
},
|
80
|
+
MuiDialogTitle: {
|
81
|
+
styleOverrides: {
|
82
|
+
root: {
|
83
|
+
padding: '8px 12px', // Further reduced from 12px 16px
|
84
|
+
},
|
85
|
+
},
|
86
|
+
},
|
87
|
+
MuiDialogContent: {
|
88
|
+
styleOverrides: {
|
89
|
+
root: {
|
90
|
+
padding: '6px 12px', // Further reduced from 8px 16px
|
91
|
+
},
|
92
|
+
},
|
93
|
+
},
|
94
|
+
MuiDialogActions: {
|
95
|
+
styleOverrides: {
|
96
|
+
root: {
|
97
|
+
padding: '6px 12px', // Further reduced from 8px 16px
|
98
|
+
},
|
99
|
+
},
|
100
|
+
},
|
101
|
+
MuiPaper: {
|
102
|
+
styleOverrides: {
|
103
|
+
root: {
|
104
|
+
padding: '8px', // Further reduced from 12px
|
105
|
+
},
|
106
|
+
},
|
107
|
+
},
|
108
|
+
MuiList: {
|
109
|
+
styleOverrides: {
|
110
|
+
root: {
|
111
|
+
padding: '2px 0', // Further reduced from 4px 0
|
112
|
+
},
|
113
|
+
},
|
114
|
+
},
|
115
|
+
MuiListItem: {
|
116
|
+
styleOverrides: {
|
117
|
+
root: {
|
118
|
+
padding: '2px 8px', // Further reduced from 4px 12px
|
119
|
+
},
|
120
|
+
},
|
121
|
+
},
|
122
|
+
MuiTableCell: {
|
123
|
+
styleOverrides: {
|
124
|
+
root: {
|
125
|
+
padding: '4px 8px', // Further reduced from 8px 12px
|
126
|
+
},
|
127
|
+
},
|
128
|
+
},
|
129
|
+
MuiCard: {
|
130
|
+
styleOverrides: {
|
131
|
+
root: {
|
132
|
+
padding: '8px',
|
133
|
+
},
|
134
|
+
},
|
135
|
+
},
|
136
|
+
MuiCardContent: {
|
137
|
+
styleOverrides: {
|
138
|
+
root: {
|
139
|
+
padding: '8px',
|
140
|
+
'&:last-child': {
|
141
|
+
paddingBottom: '8px',
|
142
|
+
},
|
143
|
+
},
|
144
|
+
},
|
145
|
+
},
|
146
|
+
MuiCardHeader: {
|
147
|
+
styleOverrides: {
|
148
|
+
root: {
|
149
|
+
padding: '8px',
|
150
|
+
},
|
151
|
+
},
|
152
|
+
},
|
153
|
+
MuiCardActions: {
|
154
|
+
styleOverrides: {
|
155
|
+
root: {
|
156
|
+
padding: '4px 8px',
|
157
|
+
},
|
158
|
+
},
|
159
|
+
},
|
160
|
+
MuiGrid: {
|
161
|
+
styleOverrides: {
|
162
|
+
container: {
|
163
|
+
marginTop: '-4px',
|
164
|
+
marginLeft: '-4px',
|
165
|
+
width: 'calc(100% + 8px)',
|
166
|
+
},
|
167
|
+
item: {
|
168
|
+
paddingTop: '4px',
|
169
|
+
paddingLeft: '4px',
|
170
|
+
},
|
171
|
+
},
|
172
|
+
},
|
173
|
+
},
|
174
|
+
spacing: (factor: number) => `${0.6 * factor}rem`, // Further reduced from 0.8 * factor
|
175
|
+
});
|
176
|
+
|
177
|
+
export default theme;
|
@@ -1 +1 @@
|
|
1
|
-
{"root":["./src/app.tsx","./src/api.ts","./src/main.tsx","./src/types.ts","./src/validation.ts","./src/vite-env.d.ts","./src/components/
|
1
|
+
{"root":["./src/app.tsx","./src/api.ts","./src/main.tsx","./src/theme.ts","./src/types.ts","./src/validation.ts","./src/vite-env.d.ts","./src/components/addlyricsmodal.tsx","./src/components/audioplayer.tsx","./src/components/correctionmetrics.tsx","./src/components/editmodal.tsx","./src/components/fileupload.tsx","./src/components/findreplacemodal.tsx","./src/components/globalsynceditor.tsx","./src/components/header.tsx","./src/components/lyricsanalyzer.tsx","./src/components/modeselector.tsx","./src/components/previewvideosection.tsx","./src/components/referenceview.tsx","./src/components/reviewchangesmodal.tsx","./src/components/segmentdetailsmodal.tsx","./src/components/timelineeditor.tsx","./src/components/transcriptionview.tsx","./src/components/worddivider.tsx","./src/components/shared/constants.ts","./src/components/shared/styles.ts","./src/components/shared/types.ts","./src/components/shared/components/highlightedtext.tsx","./src/components/shared/components/sourceselector.tsx","./src/components/shared/components/word.tsx","./src/components/shared/hooks/usewordclick.ts","./src/components/shared/utils/keyboardhandlers.ts","./src/components/shared/utils/localstorage.ts","./src/components/shared/utils/referencelinecalculator.ts","./src/components/shared/utils/segmentoperations.ts","./src/components/shared/utils/wordutils.ts","./src/hooks/usemanualsync.ts","./src/types/global.d.ts"],"version":"5.6.3"}
|
@@ -47,14 +47,14 @@ class BaseLyricsProvider(ABC):
|
|
47
47
|
converted_cache_path = self._get_cache_path(cache_key, "converted")
|
48
48
|
converted_data = self._load_from_cache(converted_cache_path)
|
49
49
|
if converted_data:
|
50
|
-
self.logger.info(f"Using cached converted lyrics for {artist} - {title}")
|
50
|
+
self.logger.info(f"Using cached converted lyrics for {artist} - {title} from file: {converted_cache_path}")
|
51
51
|
return LyricsData.from_dict(converted_data)
|
52
52
|
|
53
53
|
# Check raw cache next
|
54
54
|
raw_cache_path = self._get_cache_path(cache_key, "raw")
|
55
55
|
raw_data = self._load_from_cache(raw_cache_path)
|
56
56
|
if raw_data:
|
57
|
-
self.logger.info(f"Using cached raw lyrics for {artist} - {title}")
|
57
|
+
self.logger.info(f"Using cached raw lyrics for {artist} - {title} from file: {raw_cache_path}")
|
58
58
|
converted_result = self._convert_result_format(raw_data)
|
59
59
|
self._save_to_cache(converted_cache_path, converted_result.to_dict())
|
60
60
|
return converted_result
|
@@ -0,0 +1,44 @@
|
|
1
|
+
from typing import Optional, Dict, Any
|
2
|
+
from lyrics_transcriber.lyrics.base_lyrics_provider import BaseLyricsProvider, LyricsProviderConfig
|
3
|
+
from lyrics_transcriber.types import LyricsData, LyricsMetadata
|
4
|
+
|
5
|
+
|
6
|
+
class UserInputProvider(BaseLyricsProvider):
|
7
|
+
"""Provider for manually input lyrics text."""
|
8
|
+
|
9
|
+
def __init__(self, lyrics_text: str, source_name: str, metadata: Dict[str, Any], *args, **kwargs):
|
10
|
+
"""Initialize with the user's input text."""
|
11
|
+
super().__init__(LyricsProviderConfig(), *args, **kwargs)
|
12
|
+
self.lyrics_text = lyrics_text
|
13
|
+
self.source_name = source_name
|
14
|
+
self.input_metadata = metadata
|
15
|
+
|
16
|
+
def _fetch_data_from_source(self, artist: str, title: str) -> Optional[Dict[str, Any]]:
|
17
|
+
"""Return the user's input text as raw data."""
|
18
|
+
return {"text": self.lyrics_text, "metadata": self.input_metadata}
|
19
|
+
|
20
|
+
def _convert_result_format(self, raw_data: Dict[str, Any]) -> LyricsData:
|
21
|
+
"""Convert the raw text into LyricsData format."""
|
22
|
+
# Create segments with words from the text
|
23
|
+
segments = self._create_segments_with_words(raw_data["text"])
|
24
|
+
|
25
|
+
# Create metadata
|
26
|
+
metadata = LyricsMetadata(
|
27
|
+
source=self.source_name,
|
28
|
+
track_name=raw_data["metadata"].get("title", ""),
|
29
|
+
artist_names=raw_data["metadata"].get("artist", ""),
|
30
|
+
is_synced=False,
|
31
|
+
lyrics_provider="manual",
|
32
|
+
lyrics_provider_id="",
|
33
|
+
album_name=None,
|
34
|
+
duration_ms=None,
|
35
|
+
explicit=None,
|
36
|
+
language=None,
|
37
|
+
provider_metadata={},
|
38
|
+
)
|
39
|
+
|
40
|
+
return LyricsData(segments=segments, metadata=metadata, source=self.source_name)
|
41
|
+
|
42
|
+
def get_name(self) -> str:
|
43
|
+
"""Return the provider name."""
|
44
|
+
return "UserInput"
|
@@ -37,6 +37,7 @@ class OutputGenerator:
|
|
37
37
|
self,
|
38
38
|
config: OutputConfig,
|
39
39
|
logger: Optional[logging.Logger] = None,
|
40
|
+
preview_mode: bool = False,
|
40
41
|
):
|
41
42
|
"""
|
42
43
|
Initialize OutputGenerator with configuration.
|
@@ -44,20 +45,12 @@ class OutputGenerator:
|
|
44
45
|
Args:
|
45
46
|
config: OutputConfig instance with required paths and settings
|
46
47
|
logger: Optional logger instance
|
48
|
+
preview_mode: Boolean indicating if the generator is in preview mode
|
47
49
|
"""
|
48
50
|
self.config = config
|
49
51
|
self.logger = logger or logging.getLogger(__name__)
|
50
52
|
|
51
|
-
self.logger.
|
52
|
-
|
53
|
-
# Set video resolution parameters
|
54
|
-
self.video_resolution_num, self.font_size, self.line_height = self._get_video_params(self.config.video_resolution)
|
55
|
-
|
56
|
-
self.segment_resizer = SegmentResizer(max_line_length=self.config.max_line_length, logger=self.logger)
|
57
|
-
|
58
|
-
# Initialize generators
|
59
|
-
self.plain_text = PlainTextGenerator(self.config.output_dir, self.logger)
|
60
|
-
self.lyrics_file = LyricsFileGenerator(self.config.output_dir, self.logger)
|
53
|
+
self.logger.info(f"Initializing OutputGenerator with config: {self.config}")
|
61
54
|
|
62
55
|
if self.config.render_video or self.config.generate_cdg:
|
63
56
|
# Load output styles from JSON
|
@@ -68,10 +61,46 @@ class OutputGenerator:
|
|
68
61
|
except Exception as e:
|
69
62
|
raise ValueError(f"Failed to load output styles file: {str(e)}")
|
70
63
|
|
64
|
+
# Set video resolution parameters
|
65
|
+
self.video_resolution_num, self.font_size, self.line_height = self._get_video_params(self.config.video_resolution)
|
66
|
+
self.logger.info(f"Video resolution: {self.video_resolution_num}, font size: {self.font_size}, line height: {self.line_height}")
|
67
|
+
|
68
|
+
self.segment_resizer = SegmentResizer(max_line_length=self.config.max_line_length, logger=self.logger)
|
69
|
+
|
70
|
+
# Initialize generators
|
71
|
+
self.plain_text = PlainTextGenerator(self.config.output_dir, self.logger)
|
72
|
+
self.lyrics_file = LyricsFileGenerator(self.config.output_dir, self.logger)
|
73
|
+
|
71
74
|
if self.config.generate_cdg:
|
72
75
|
self.cdg = CDGGenerator(self.config.output_dir, self.logger)
|
73
76
|
|
77
|
+
self.preview_mode = preview_mode
|
74
78
|
if self.config.render_video:
|
79
|
+
# Apply preview mode scaling if needed
|
80
|
+
if self.preview_mode:
|
81
|
+
# Scale down from 4K (2160p) to 360p - factor of 1/6
|
82
|
+
scale_factor = 1 / 6
|
83
|
+
|
84
|
+
# Scale down top padding for preview if it exists
|
85
|
+
if "karaoke" in self.config.styles and "top_padding" in self.config.styles["karaoke"]:
|
86
|
+
self.logger.info(f"Preview mode: Found top_padding: {self.config.styles['karaoke']['top_padding']}")
|
87
|
+
original_padding = self.config.styles["karaoke"]["top_padding"]
|
88
|
+
if original_padding is not None:
|
89
|
+
# Scale down from 4K (2160p) to 360p - factor of 1/6
|
90
|
+
self.config.styles["karaoke"]["top_padding"] = original_padding * scale_factor
|
91
|
+
self.logger.info(f"Preview mode: Scaled down top_padding to: {self.config.styles['karaoke']['top_padding']}")
|
92
|
+
|
93
|
+
# Scale down font size for preview if it exists
|
94
|
+
if "karaoke" in self.config.styles and "font_size" in self.config.styles["karaoke"]:
|
95
|
+
self.logger.info(f"Preview mode: Found font_size: {self.config.styles['karaoke']['font_size']}")
|
96
|
+
original_font_size = self.config.styles["karaoke"]["font_size"]
|
97
|
+
if original_font_size is not None:
|
98
|
+
# Scale down from 4K (2160p) to 360p - factor of 1/6
|
99
|
+
self.font_size = original_font_size * scale_factor
|
100
|
+
self.config.styles["karaoke"]["font_size"] = self.font_size
|
101
|
+
self.logger.info(f"Preview mode: Scaled down font_size to: {self.font_size}")
|
102
|
+
|
103
|
+
# Initialize subtitle generator with potentially scaled values
|
75
104
|
self.subtitle = SubtitlesGenerator(
|
76
105
|
output_dir=self.config.output_dir,
|
77
106
|
video_resolution=self.video_resolution_num,
|
@@ -102,7 +131,6 @@ class OutputGenerator:
|
|
102
131
|
audio_filepath: str,
|
103
132
|
artist: Optional[str] = None,
|
104
133
|
title: Optional[str] = None,
|
105
|
-
preview_mode: bool = False,
|
106
134
|
) -> OutputPaths:
|
107
135
|
"""Generate all requested output formats."""
|
108
136
|
outputs = OutputPaths()
|
@@ -116,7 +144,7 @@ class OutputGenerator:
|
|
116
144
|
transcription_corrected.resized_segments = resized_segments
|
117
145
|
|
118
146
|
# For preview, we only need to generate ASS and video
|
119
|
-
if preview_mode:
|
147
|
+
if self.preview_mode:
|
120
148
|
# Generate ASS subtitles for preview
|
121
149
|
outputs.ass = self.subtitle.generate_ass(transcription_corrected.resized_segments, output_prefix, audio_filepath)
|
122
150
|
|
@@ -63,8 +63,12 @@ class VideoGenerator:
|
|
63
63
|
raise FileNotFoundError(f"Audio file not found: {audio_path}")
|
64
64
|
|
65
65
|
try:
|
66
|
-
# Create a temporary copy of the ASS file with a
|
67
|
-
|
66
|
+
# Create a temporary copy of the ASS file with a unique filename
|
67
|
+
import time
|
68
|
+
|
69
|
+
safe_prefix = "".join(c if c.isalnum() else "_" for c in output_prefix)
|
70
|
+
timestamp = int(time.time() * 1000)
|
71
|
+
temp_ass_path = os.path.join(self.cache_dir, f"temp_subtitles_{safe_prefix}_{timestamp}.ass")
|
68
72
|
import shutil
|
69
73
|
|
70
74
|
shutil.copy2(ass_path, temp_ass_path)
|
@@ -75,13 +79,14 @@ class VideoGenerator:
|
|
75
79
|
self.logger.info(f"Video generated: {output_path}")
|
76
80
|
|
77
81
|
# Clean up temporary file
|
78
|
-
os.
|
82
|
+
if os.path.exists(temp_ass_path):
|
83
|
+
os.remove(temp_ass_path)
|
79
84
|
return output_path
|
80
85
|
|
81
86
|
except Exception as e:
|
82
87
|
self.logger.error(f"Failed to generate video: {str(e)}")
|
83
88
|
# Clean up temporary file in case of error
|
84
|
-
if "temp_ass_path" in locals():
|
89
|
+
if "temp_ass_path" in locals() and os.path.exists(temp_ass_path):
|
85
90
|
try:
|
86
91
|
os.remove(temp_ass_path)
|
87
92
|
except:
|
@@ -109,8 +114,12 @@ class VideoGenerator:
|
|
109
114
|
raise FileNotFoundError(f"Audio file not found: {audio_path}")
|
110
115
|
|
111
116
|
try:
|
112
|
-
# Create a temporary copy of the ASS file with a
|
113
|
-
|
117
|
+
# Create a temporary copy of the ASS file with a unique filename
|
118
|
+
import time
|
119
|
+
|
120
|
+
safe_prefix = "".join(c if c.isalnum() else "_" for c in output_prefix)
|
121
|
+
timestamp = int(time.time() * 1000)
|
122
|
+
temp_ass_path = os.path.join(self.cache_dir, f"temp_preview_subtitles_{safe_prefix}_{timestamp}.ass")
|
114
123
|
import shutil
|
115
124
|
|
116
125
|
shutil.copy2(ass_path, temp_ass_path)
|
@@ -121,13 +130,14 @@ class VideoGenerator:
|
|
121
130
|
self.logger.info(f"Preview video generated: {output_path}")
|
122
131
|
|
123
132
|
# Clean up temporary file
|
124
|
-
os.
|
133
|
+
if os.path.exists(temp_ass_path):
|
134
|
+
os.remove(temp_ass_path)
|
125
135
|
return output_path
|
126
136
|
|
127
137
|
except Exception as e:
|
128
138
|
self.logger.error(f"Failed to generate preview video: {str(e)}")
|
129
139
|
# Clean up temporary file in case of error
|
130
|
-
if "temp_ass_path" in locals():
|
140
|
+
if "temp_ass_path" in locals() and os.path.exists(temp_ass_path):
|
131
141
|
try:
|
132
142
|
os.remove(temp_ass_path)
|
133
143
|
except:
|