karaoke-gen 0.75.54__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.
Potentially problematic release.
This version of karaoke-gen might be problematic. Click here for more details.
- karaoke_gen/__init__.py +38 -0
- karaoke_gen/audio_fetcher.py +1614 -0
- karaoke_gen/audio_processor.py +790 -0
- karaoke_gen/config.py +83 -0
- karaoke_gen/file_handler.py +387 -0
- karaoke_gen/instrumental_review/__init__.py +45 -0
- karaoke_gen/instrumental_review/analyzer.py +408 -0
- karaoke_gen/instrumental_review/editor.py +322 -0
- karaoke_gen/instrumental_review/models.py +171 -0
- karaoke_gen/instrumental_review/server.py +475 -0
- karaoke_gen/instrumental_review/static/index.html +1529 -0
- karaoke_gen/instrumental_review/waveform.py +409 -0
- karaoke_gen/karaoke_finalise/__init__.py +1 -0
- karaoke_gen/karaoke_finalise/karaoke_finalise.py +1833 -0
- karaoke_gen/karaoke_gen.py +1026 -0
- karaoke_gen/lyrics_processor.py +474 -0
- karaoke_gen/metadata.py +160 -0
- karaoke_gen/pipeline/__init__.py +87 -0
- karaoke_gen/pipeline/base.py +215 -0
- karaoke_gen/pipeline/context.py +230 -0
- karaoke_gen/pipeline/executors/__init__.py +21 -0
- karaoke_gen/pipeline/executors/local.py +159 -0
- karaoke_gen/pipeline/executors/remote.py +257 -0
- karaoke_gen/pipeline/stages/__init__.py +27 -0
- karaoke_gen/pipeline/stages/finalize.py +202 -0
- karaoke_gen/pipeline/stages/render.py +165 -0
- karaoke_gen/pipeline/stages/screens.py +139 -0
- karaoke_gen/pipeline/stages/separation.py +191 -0
- karaoke_gen/pipeline/stages/transcription.py +191 -0
- karaoke_gen/resources/AvenirNext-Bold.ttf +0 -0
- karaoke_gen/resources/Montserrat-Bold.ttf +0 -0
- karaoke_gen/resources/Oswald-Bold.ttf +0 -0
- karaoke_gen/resources/Oswald-SemiBold.ttf +0 -0
- karaoke_gen/resources/Zurich_Cn_BT_Bold.ttf +0 -0
- karaoke_gen/style_loader.py +531 -0
- karaoke_gen/utils/__init__.py +18 -0
- karaoke_gen/utils/bulk_cli.py +492 -0
- karaoke_gen/utils/cli_args.py +432 -0
- karaoke_gen/utils/gen_cli.py +978 -0
- karaoke_gen/utils/remote_cli.py +3268 -0
- karaoke_gen/video_background_processor.py +351 -0
- karaoke_gen/video_generator.py +424 -0
- karaoke_gen-0.75.54.dist-info/METADATA +718 -0
- karaoke_gen-0.75.54.dist-info/RECORD +287 -0
- karaoke_gen-0.75.54.dist-info/WHEEL +4 -0
- karaoke_gen-0.75.54.dist-info/entry_points.txt +5 -0
- karaoke_gen-0.75.54.dist-info/licenses/LICENSE +21 -0
- lyrics_transcriber/__init__.py +10 -0
- lyrics_transcriber/cli/__init__.py +0 -0
- lyrics_transcriber/cli/cli_main.py +285 -0
- lyrics_transcriber/core/__init__.py +0 -0
- lyrics_transcriber/core/config.py +50 -0
- lyrics_transcriber/core/controller.py +594 -0
- lyrics_transcriber/correction/__init__.py +0 -0
- lyrics_transcriber/correction/agentic/__init__.py +9 -0
- lyrics_transcriber/correction/agentic/adapter.py +71 -0
- lyrics_transcriber/correction/agentic/agent.py +313 -0
- lyrics_transcriber/correction/agentic/feedback/aggregator.py +12 -0
- lyrics_transcriber/correction/agentic/feedback/collector.py +17 -0
- lyrics_transcriber/correction/agentic/feedback/retention.py +24 -0
- lyrics_transcriber/correction/agentic/feedback/store.py +76 -0
- lyrics_transcriber/correction/agentic/handlers/__init__.py +24 -0
- lyrics_transcriber/correction/agentic/handlers/ambiguous.py +44 -0
- lyrics_transcriber/correction/agentic/handlers/background_vocals.py +68 -0
- lyrics_transcriber/correction/agentic/handlers/base.py +51 -0
- lyrics_transcriber/correction/agentic/handlers/complex_multi_error.py +46 -0
- lyrics_transcriber/correction/agentic/handlers/extra_words.py +74 -0
- lyrics_transcriber/correction/agentic/handlers/no_error.py +42 -0
- lyrics_transcriber/correction/agentic/handlers/punctuation.py +44 -0
- lyrics_transcriber/correction/agentic/handlers/registry.py +60 -0
- lyrics_transcriber/correction/agentic/handlers/repeated_section.py +44 -0
- lyrics_transcriber/correction/agentic/handlers/sound_alike.py +126 -0
- lyrics_transcriber/correction/agentic/models/__init__.py +5 -0
- lyrics_transcriber/correction/agentic/models/ai_correction.py +31 -0
- lyrics_transcriber/correction/agentic/models/correction_session.py +30 -0
- lyrics_transcriber/correction/agentic/models/enums.py +38 -0
- lyrics_transcriber/correction/agentic/models/human_feedback.py +30 -0
- lyrics_transcriber/correction/agentic/models/learning_data.py +26 -0
- lyrics_transcriber/correction/agentic/models/observability_metrics.py +28 -0
- lyrics_transcriber/correction/agentic/models/schemas.py +46 -0
- lyrics_transcriber/correction/agentic/models/utils.py +19 -0
- lyrics_transcriber/correction/agentic/observability/__init__.py +5 -0
- lyrics_transcriber/correction/agentic/observability/langfuse_integration.py +35 -0
- lyrics_transcriber/correction/agentic/observability/metrics.py +46 -0
- lyrics_transcriber/correction/agentic/observability/performance.py +19 -0
- lyrics_transcriber/correction/agentic/prompts/__init__.py +2 -0
- lyrics_transcriber/correction/agentic/prompts/classifier.py +227 -0
- lyrics_transcriber/correction/agentic/providers/__init__.py +6 -0
- lyrics_transcriber/correction/agentic/providers/base.py +36 -0
- lyrics_transcriber/correction/agentic/providers/circuit_breaker.py +145 -0
- lyrics_transcriber/correction/agentic/providers/config.py +73 -0
- lyrics_transcriber/correction/agentic/providers/constants.py +24 -0
- lyrics_transcriber/correction/agentic/providers/health.py +28 -0
- lyrics_transcriber/correction/agentic/providers/langchain_bridge.py +212 -0
- lyrics_transcriber/correction/agentic/providers/model_factory.py +209 -0
- lyrics_transcriber/correction/agentic/providers/response_cache.py +218 -0
- lyrics_transcriber/correction/agentic/providers/response_parser.py +111 -0
- lyrics_transcriber/correction/agentic/providers/retry_executor.py +127 -0
- lyrics_transcriber/correction/agentic/router.py +35 -0
- lyrics_transcriber/correction/agentic/workflows/__init__.py +5 -0
- lyrics_transcriber/correction/agentic/workflows/consensus_workflow.py +24 -0
- lyrics_transcriber/correction/agentic/workflows/correction_graph.py +59 -0
- lyrics_transcriber/correction/agentic/workflows/feedback_workflow.py +24 -0
- lyrics_transcriber/correction/anchor_sequence.py +919 -0
- lyrics_transcriber/correction/corrector.py +760 -0
- lyrics_transcriber/correction/feedback/__init__.py +2 -0
- lyrics_transcriber/correction/feedback/schemas.py +107 -0
- lyrics_transcriber/correction/feedback/store.py +236 -0
- lyrics_transcriber/correction/handlers/__init__.py +0 -0
- lyrics_transcriber/correction/handlers/base.py +52 -0
- lyrics_transcriber/correction/handlers/extend_anchor.py +149 -0
- lyrics_transcriber/correction/handlers/levenshtein.py +189 -0
- lyrics_transcriber/correction/handlers/llm.py +293 -0
- lyrics_transcriber/correction/handlers/llm_providers.py +60 -0
- lyrics_transcriber/correction/handlers/no_space_punct_match.py +154 -0
- lyrics_transcriber/correction/handlers/relaxed_word_count_match.py +85 -0
- lyrics_transcriber/correction/handlers/repeat.py +88 -0
- lyrics_transcriber/correction/handlers/sound_alike.py +259 -0
- lyrics_transcriber/correction/handlers/syllables_match.py +252 -0
- lyrics_transcriber/correction/handlers/word_count_match.py +80 -0
- lyrics_transcriber/correction/handlers/word_operations.py +187 -0
- lyrics_transcriber/correction/operations.py +352 -0
- lyrics_transcriber/correction/phrase_analyzer.py +435 -0
- lyrics_transcriber/correction/text_utils.py +30 -0
- lyrics_transcriber/frontend/.gitignore +23 -0
- lyrics_transcriber/frontend/.yarn/releases/yarn-4.7.0.cjs +935 -0
- lyrics_transcriber/frontend/.yarnrc.yml +3 -0
- lyrics_transcriber/frontend/README.md +50 -0
- lyrics_transcriber/frontend/REPLACE_ALL_FUNCTIONALITY.md +210 -0
- lyrics_transcriber/frontend/__init__.py +25 -0
- lyrics_transcriber/frontend/eslint.config.js +28 -0
- lyrics_transcriber/frontend/index.html +18 -0
- lyrics_transcriber/frontend/package.json +42 -0
- lyrics_transcriber/frontend/public/android-chrome-192x192.png +0 -0
- lyrics_transcriber/frontend/public/android-chrome-512x512.png +0 -0
- lyrics_transcriber/frontend/public/apple-touch-icon.png +0 -0
- lyrics_transcriber/frontend/public/favicon-16x16.png +0 -0
- lyrics_transcriber/frontend/public/favicon-32x32.png +0 -0
- lyrics_transcriber/frontend/public/favicon.ico +0 -0
- lyrics_transcriber/frontend/public/nomad-karaoke-logo.png +0 -0
- lyrics_transcriber/frontend/src/App.tsx +214 -0
- lyrics_transcriber/frontend/src/api.ts +254 -0
- lyrics_transcriber/frontend/src/components/AIFeedbackModal.tsx +77 -0
- lyrics_transcriber/frontend/src/components/AddLyricsModal.tsx +114 -0
- lyrics_transcriber/frontend/src/components/AgenticCorrectionMetrics.tsx +204 -0
- lyrics_transcriber/frontend/src/components/AudioPlayer.tsx +180 -0
- lyrics_transcriber/frontend/src/components/CorrectedWordWithActions.tsx +167 -0
- lyrics_transcriber/frontend/src/components/CorrectionAnnotationModal.tsx +359 -0
- lyrics_transcriber/frontend/src/components/CorrectionDetailCard.tsx +281 -0
- lyrics_transcriber/frontend/src/components/CorrectionMetrics.tsx +162 -0
- lyrics_transcriber/frontend/src/components/DurationTimelineView.tsx +257 -0
- lyrics_transcriber/frontend/src/components/EditActionBar.tsx +68 -0
- lyrics_transcriber/frontend/src/components/EditModal.tsx +702 -0
- lyrics_transcriber/frontend/src/components/EditTimelineSection.tsx +496 -0
- lyrics_transcriber/frontend/src/components/EditWordList.tsx +379 -0
- lyrics_transcriber/frontend/src/components/FileUpload.tsx +77 -0
- lyrics_transcriber/frontend/src/components/FindReplaceModal.tsx +467 -0
- lyrics_transcriber/frontend/src/components/Header.tsx +413 -0
- lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +1387 -0
- lyrics_transcriber/frontend/src/components/LyricsSynchronizer/SyncControls.tsx +185 -0
- lyrics_transcriber/frontend/src/components/LyricsSynchronizer/TimelineCanvas.tsx +704 -0
- lyrics_transcriber/frontend/src/components/LyricsSynchronizer/UpcomingWordsBar.tsx +80 -0
- lyrics_transcriber/frontend/src/components/LyricsSynchronizer/index.tsx +905 -0
- lyrics_transcriber/frontend/src/components/MetricsDashboard.tsx +51 -0
- lyrics_transcriber/frontend/src/components/ModeSelectionModal.tsx +127 -0
- lyrics_transcriber/frontend/src/components/ModeSelector.tsx +67 -0
- lyrics_transcriber/frontend/src/components/ModelSelector.tsx +23 -0
- lyrics_transcriber/frontend/src/components/PreviewVideoSection.tsx +144 -0
- lyrics_transcriber/frontend/src/components/ReferenceView.tsx +268 -0
- lyrics_transcriber/frontend/src/components/ReplaceAllLyricsModal.tsx +336 -0
- lyrics_transcriber/frontend/src/components/ReviewChangesModal.tsx +354 -0
- lyrics_transcriber/frontend/src/components/SegmentDetailsModal.tsx +64 -0
- lyrics_transcriber/frontend/src/components/TimelineEditor.tsx +376 -0
- lyrics_transcriber/frontend/src/components/TimingOffsetModal.tsx +131 -0
- lyrics_transcriber/frontend/src/components/TranscriptionView.tsx +256 -0
- lyrics_transcriber/frontend/src/components/WordDivider.tsx +187 -0
- lyrics_transcriber/frontend/src/components/shared/components/HighlightedText.tsx +379 -0
- lyrics_transcriber/frontend/src/components/shared/components/SourceSelector.tsx +56 -0
- lyrics_transcriber/frontend/src/components/shared/components/Word.tsx +87 -0
- lyrics_transcriber/frontend/src/components/shared/constants.ts +20 -0
- lyrics_transcriber/frontend/src/components/shared/hooks/useWordClick.ts +180 -0
- lyrics_transcriber/frontend/src/components/shared/styles.ts +13 -0
- lyrics_transcriber/frontend/src/components/shared/types.js +2 -0
- lyrics_transcriber/frontend/src/components/shared/types.ts +129 -0
- lyrics_transcriber/frontend/src/components/shared/utils/keyboardHandlers.ts +177 -0
- lyrics_transcriber/frontend/src/components/shared/utils/localStorage.ts +78 -0
- lyrics_transcriber/frontend/src/components/shared/utils/referenceLineCalculator.ts +75 -0
- lyrics_transcriber/frontend/src/components/shared/utils/segmentOperations.ts +360 -0
- lyrics_transcriber/frontend/src/components/shared/utils/timingUtils.ts +110 -0
- lyrics_transcriber/frontend/src/components/shared/utils/wordUtils.ts +22 -0
- lyrics_transcriber/frontend/src/hooks/useManualSync.ts +435 -0
- lyrics_transcriber/frontend/src/main.tsx +17 -0
- lyrics_transcriber/frontend/src/theme.ts +177 -0
- lyrics_transcriber/frontend/src/types/global.d.ts +9 -0
- lyrics_transcriber/frontend/src/types.js +2 -0
- lyrics_transcriber/frontend/src/types.ts +199 -0
- lyrics_transcriber/frontend/src/validation.ts +132 -0
- lyrics_transcriber/frontend/src/vite-env.d.ts +1 -0
- lyrics_transcriber/frontend/tsconfig.app.json +26 -0
- lyrics_transcriber/frontend/tsconfig.json +25 -0
- lyrics_transcriber/frontend/tsconfig.node.json +23 -0
- lyrics_transcriber/frontend/tsconfig.tsbuildinfo +1 -0
- lyrics_transcriber/frontend/update_version.js +11 -0
- lyrics_transcriber/frontend/vite.config.d.ts +2 -0
- lyrics_transcriber/frontend/vite.config.js +10 -0
- lyrics_transcriber/frontend/vite.config.ts +11 -0
- lyrics_transcriber/frontend/web_assets/android-chrome-192x192.png +0 -0
- lyrics_transcriber/frontend/web_assets/android-chrome-512x512.png +0 -0
- lyrics_transcriber/frontend/web_assets/apple-touch-icon.png +0 -0
- lyrics_transcriber/frontend/web_assets/assets/index-BECn1o8Q.js +43288 -0
- lyrics_transcriber/frontend/web_assets/assets/index-BECn1o8Q.js.map +1 -0
- lyrics_transcriber/frontend/web_assets/favicon-16x16.png +0 -0
- lyrics_transcriber/frontend/web_assets/favicon-32x32.png +0 -0
- lyrics_transcriber/frontend/web_assets/favicon.ico +0 -0
- lyrics_transcriber/frontend/web_assets/index.html +18 -0
- lyrics_transcriber/frontend/web_assets/nomad-karaoke-logo.png +0 -0
- lyrics_transcriber/frontend/yarn.lock +3752 -0
- lyrics_transcriber/lyrics/__init__.py +0 -0
- lyrics_transcriber/lyrics/base_lyrics_provider.py +211 -0
- lyrics_transcriber/lyrics/file_provider.py +95 -0
- lyrics_transcriber/lyrics/genius.py +384 -0
- lyrics_transcriber/lyrics/lrclib.py +231 -0
- lyrics_transcriber/lyrics/musixmatch.py +156 -0
- lyrics_transcriber/lyrics/spotify.py +290 -0
- lyrics_transcriber/lyrics/user_input_provider.py +44 -0
- lyrics_transcriber/output/__init__.py +0 -0
- lyrics_transcriber/output/ass/__init__.py +21 -0
- lyrics_transcriber/output/ass/ass.py +2088 -0
- lyrics_transcriber/output/ass/ass_specs.txt +732 -0
- lyrics_transcriber/output/ass/config.py +180 -0
- lyrics_transcriber/output/ass/constants.py +23 -0
- lyrics_transcriber/output/ass/event.py +94 -0
- lyrics_transcriber/output/ass/formatters.py +132 -0
- lyrics_transcriber/output/ass/lyrics_line.py +265 -0
- lyrics_transcriber/output/ass/lyrics_screen.py +252 -0
- lyrics_transcriber/output/ass/section_detector.py +89 -0
- lyrics_transcriber/output/ass/section_screen.py +106 -0
- lyrics_transcriber/output/ass/style.py +187 -0
- lyrics_transcriber/output/cdg.py +619 -0
- lyrics_transcriber/output/cdgmaker/__init__.py +0 -0
- lyrics_transcriber/output/cdgmaker/cdg.py +262 -0
- lyrics_transcriber/output/cdgmaker/composer.py +2260 -0
- lyrics_transcriber/output/cdgmaker/config.py +151 -0
- lyrics_transcriber/output/cdgmaker/images/instrumental.png +0 -0
- lyrics_transcriber/output/cdgmaker/images/intro.png +0 -0
- lyrics_transcriber/output/cdgmaker/pack.py +507 -0
- lyrics_transcriber/output/cdgmaker/render.py +346 -0
- lyrics_transcriber/output/cdgmaker/transitions/centertexttoplogobottomtext.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/circlein.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/circleout.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/fizzle.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/largecentertexttoplogo.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/rectangle.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/spiral.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/topleftmusicalnotes.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/wipein.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/wipeleft.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/wipeout.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/wiperight.png +0 -0
- lyrics_transcriber/output/cdgmaker/utils.py +132 -0
- lyrics_transcriber/output/countdown_processor.py +306 -0
- lyrics_transcriber/output/fonts/AvenirNext-Bold.ttf +0 -0
- lyrics_transcriber/output/fonts/DMSans-VariableFont_opsz,wght.ttf +0 -0
- lyrics_transcriber/output/fonts/DMSerifDisplay-Regular.ttf +0 -0
- lyrics_transcriber/output/fonts/Oswald-SemiBold.ttf +0 -0
- lyrics_transcriber/output/fonts/Zurich_Cn_BT_Bold.ttf +0 -0
- lyrics_transcriber/output/fonts/arial.ttf +0 -0
- lyrics_transcriber/output/fonts/georgia.ttf +0 -0
- lyrics_transcriber/output/fonts/verdana.ttf +0 -0
- lyrics_transcriber/output/generator.py +257 -0
- lyrics_transcriber/output/lrc_to_cdg.py +61 -0
- lyrics_transcriber/output/lyrics_file.py +102 -0
- lyrics_transcriber/output/plain_text.py +96 -0
- lyrics_transcriber/output/segment_resizer.py +431 -0
- lyrics_transcriber/output/subtitles.py +397 -0
- lyrics_transcriber/output/video.py +544 -0
- lyrics_transcriber/review/__init__.py +0 -0
- lyrics_transcriber/review/server.py +676 -0
- lyrics_transcriber/storage/__init__.py +0 -0
- lyrics_transcriber/storage/dropbox.py +225 -0
- lyrics_transcriber/transcribers/__init__.py +0 -0
- lyrics_transcriber/transcribers/audioshake.py +379 -0
- lyrics_transcriber/transcribers/base_transcriber.py +157 -0
- lyrics_transcriber/transcribers/whisper.py +330 -0
- lyrics_transcriber/types.py +650 -0
- lyrics_transcriber/utils/__init__.py +0 -0
- lyrics_transcriber/utils/word_utils.py +27 -0
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unified style loading and configuration module.
|
|
3
|
+
|
|
4
|
+
This module provides a single source of truth for:
|
|
5
|
+
- Default style configurations (intro, karaoke, end, cdg)
|
|
6
|
+
- Asset key mappings (GCS asset keys -> style JSON paths)
|
|
7
|
+
- Style loading from local files
|
|
8
|
+
- Style loading from GCS with asset path updates
|
|
9
|
+
|
|
10
|
+
Used by both the local CLI (karaoke-gen) and the cloud backend workers.
|
|
11
|
+
"""
|
|
12
|
+
import json
|
|
13
|
+
import logging
|
|
14
|
+
import os
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# =============================================================================
|
|
23
|
+
# DEFAULT STYLE CONFIGURATIONS
|
|
24
|
+
# =============================================================================
|
|
25
|
+
|
|
26
|
+
DEFAULT_INTRO_STYLE = {
|
|
27
|
+
"video_duration": 5,
|
|
28
|
+
"existing_image": None,
|
|
29
|
+
"background_color": "#000000",
|
|
30
|
+
"background_image": None,
|
|
31
|
+
"font": "Montserrat-Bold.ttf",
|
|
32
|
+
"artist_color": "#ffdf6b",
|
|
33
|
+
"artist_gradient": None,
|
|
34
|
+
"artist_text_transform": None,
|
|
35
|
+
"title_color": "#ffffff",
|
|
36
|
+
"title_gradient": None,
|
|
37
|
+
"title_text_transform": None,
|
|
38
|
+
"title_region": "370, 200, 3100, 480",
|
|
39
|
+
"artist_region": "370, 700, 3100, 480",
|
|
40
|
+
"extra_text": None,
|
|
41
|
+
"extra_text_color": "#ffffff",
|
|
42
|
+
"extra_text_gradient": None,
|
|
43
|
+
"extra_text_region": "370, 1200, 3100, 480",
|
|
44
|
+
"extra_text_text_transform": None,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
DEFAULT_END_STYLE = {
|
|
48
|
+
"video_duration": 5,
|
|
49
|
+
"existing_image": None,
|
|
50
|
+
"background_color": "#000000",
|
|
51
|
+
"background_image": None,
|
|
52
|
+
"font": "Montserrat-Bold.ttf",
|
|
53
|
+
"artist_color": "#ffdf6b",
|
|
54
|
+
"artist_gradient": None,
|
|
55
|
+
"artist_text_transform": None,
|
|
56
|
+
"title_color": "#ffffff",
|
|
57
|
+
"title_gradient": None,
|
|
58
|
+
"title_text_transform": None,
|
|
59
|
+
"title_region": None,
|
|
60
|
+
"artist_region": None,
|
|
61
|
+
"extra_text": "THANK YOU FOR SINGING!",
|
|
62
|
+
"extra_text_color": "#ff7acc",
|
|
63
|
+
"extra_text_gradient": None,
|
|
64
|
+
"extra_text_region": None,
|
|
65
|
+
"extra_text_text_transform": None,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
DEFAULT_KARAOKE_STYLE = {
|
|
69
|
+
# Video background
|
|
70
|
+
"background_color": "#000000",
|
|
71
|
+
"background_image": None,
|
|
72
|
+
# Font settings
|
|
73
|
+
"font": "Arial",
|
|
74
|
+
"font_path": "", # Must be string, not None (for ASS generator)
|
|
75
|
+
"ass_name": "Default",
|
|
76
|
+
# Colors in "R, G, B, A" format (required by ASS)
|
|
77
|
+
"primary_color": "112, 112, 247, 255",
|
|
78
|
+
"secondary_color": "255, 255, 255, 255",
|
|
79
|
+
"outline_color": "26, 58, 235, 255",
|
|
80
|
+
"back_color": "0, 0, 0, 0",
|
|
81
|
+
# Boolean style options
|
|
82
|
+
"bold": False,
|
|
83
|
+
"italic": False,
|
|
84
|
+
"underline": False,
|
|
85
|
+
"strike_out": False,
|
|
86
|
+
# Numeric style options (all required for ASS)
|
|
87
|
+
"scale_x": 100,
|
|
88
|
+
"scale_y": 100,
|
|
89
|
+
"spacing": 0,
|
|
90
|
+
"angle": 0.0,
|
|
91
|
+
"border_style": 1,
|
|
92
|
+
"outline": 1,
|
|
93
|
+
"shadow": 0,
|
|
94
|
+
"margin_l": 0,
|
|
95
|
+
"margin_r": 0,
|
|
96
|
+
"margin_v": 0,
|
|
97
|
+
"encoding": 0,
|
|
98
|
+
# Layout settings
|
|
99
|
+
"max_line_length": 40,
|
|
100
|
+
"top_padding": 200,
|
|
101
|
+
"font_size": 100,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
DEFAULT_CDG_STYLE = {
|
|
105
|
+
"font_path": None,
|
|
106
|
+
"instrumental_background": None,
|
|
107
|
+
"title_screen_background": None,
|
|
108
|
+
"outro_background": None,
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
# Combined defaults for convenience
|
|
112
|
+
DEFAULT_STYLE_PARAMS = {
|
|
113
|
+
"intro": DEFAULT_INTRO_STYLE.copy(),
|
|
114
|
+
"end": DEFAULT_END_STYLE.copy(),
|
|
115
|
+
"karaoke": DEFAULT_KARAOKE_STYLE.copy(),
|
|
116
|
+
"cdg": DEFAULT_CDG_STYLE.copy(),
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
# =============================================================================
|
|
121
|
+
# ASSET KEY MAPPINGS
|
|
122
|
+
# =============================================================================
|
|
123
|
+
|
|
124
|
+
# Maps GCS/upload asset keys to (section, field) paths in the style JSON.
|
|
125
|
+
# Some assets map to multiple fields (e.g., font applies to intro, karaoke, end).
|
|
126
|
+
ASSET_KEY_MAPPINGS: Dict[str, Union[Tuple[str, str], List[Tuple[str, str]]]] = {
|
|
127
|
+
# Background images - each maps to one section
|
|
128
|
+
"intro_background": ("intro", "background_image"),
|
|
129
|
+
"style_intro_background": ("intro", "background_image"), # CLI upload key
|
|
130
|
+
"karaoke_background": ("karaoke", "background_image"),
|
|
131
|
+
"style_karaoke_background": ("karaoke", "background_image"), # CLI upload key
|
|
132
|
+
"end_background": ("end", "background_image"),
|
|
133
|
+
"style_end_background": ("end", "background_image"), # CLI upload key
|
|
134
|
+
|
|
135
|
+
# Font - maps to multiple sections
|
|
136
|
+
"font": [
|
|
137
|
+
("intro", "font"),
|
|
138
|
+
("karaoke", "font_path"),
|
|
139
|
+
("end", "font"),
|
|
140
|
+
("cdg", "font_path"),
|
|
141
|
+
],
|
|
142
|
+
"style_font": [ # CLI upload key
|
|
143
|
+
("intro", "font"),
|
|
144
|
+
("karaoke", "font_path"),
|
|
145
|
+
("end", "font"),
|
|
146
|
+
("cdg", "font_path"),
|
|
147
|
+
],
|
|
148
|
+
|
|
149
|
+
# CDG-specific backgrounds
|
|
150
|
+
"cdg_instrumental_background": ("cdg", "instrumental_background"),
|
|
151
|
+
"style_cdg_instrumental_background": ("cdg", "instrumental_background"),
|
|
152
|
+
"cdg_title_background": ("cdg", "title_screen_background"),
|
|
153
|
+
"style_cdg_title_background": ("cdg", "title_screen_background"),
|
|
154
|
+
"cdg_outro_background": ("cdg", "outro_background"),
|
|
155
|
+
"style_cdg_outro_background": ("cdg", "outro_background"),
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
# =============================================================================
|
|
160
|
+
# STYLE LOADING FUNCTIONS
|
|
161
|
+
# =============================================================================
|
|
162
|
+
|
|
163
|
+
def load_style_params_from_file(
|
|
164
|
+
style_json_path: Optional[str],
|
|
165
|
+
logger: Optional[logging.Logger] = None,
|
|
166
|
+
exit_on_error: bool = True,
|
|
167
|
+
) -> Dict[str, Any]:
|
|
168
|
+
"""
|
|
169
|
+
Load style parameters from a local JSON file.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
style_json_path: Path to the style JSON file, or None for defaults.
|
|
173
|
+
logger: Optional logger for messages.
|
|
174
|
+
exit_on_error: If True, calls sys.exit(1) on file errors.
|
|
175
|
+
If False, raises exceptions instead.
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
Dictionary of style parameters (loaded or defaults).
|
|
179
|
+
|
|
180
|
+
Raises:
|
|
181
|
+
FileNotFoundError: If file not found and exit_on_error=False.
|
|
182
|
+
json.JSONDecodeError: If invalid JSON and exit_on_error=False.
|
|
183
|
+
"""
|
|
184
|
+
import sys
|
|
185
|
+
|
|
186
|
+
log = logger or logging.getLogger(__name__)
|
|
187
|
+
|
|
188
|
+
if not style_json_path:
|
|
189
|
+
log.info("No style parameters JSON file provided. Using default styles.")
|
|
190
|
+
return get_default_style_params()
|
|
191
|
+
|
|
192
|
+
try:
|
|
193
|
+
with open(style_json_path, "r") as f:
|
|
194
|
+
style_params = json.load(f)
|
|
195
|
+
log.info(f"Loaded style parameters from {style_json_path}")
|
|
196
|
+
return style_params
|
|
197
|
+
except FileNotFoundError:
|
|
198
|
+
log.error(f"Style parameters configuration file not found: {style_json_path}")
|
|
199
|
+
if exit_on_error:
|
|
200
|
+
sys.exit(1)
|
|
201
|
+
raise
|
|
202
|
+
except json.JSONDecodeError as e:
|
|
203
|
+
log.error(f"Invalid JSON in style parameters configuration file: {e}")
|
|
204
|
+
if exit_on_error:
|
|
205
|
+
sys.exit(1)
|
|
206
|
+
raise
|
|
207
|
+
except Exception as e:
|
|
208
|
+
log.error(f"Error loading style parameters file {style_json_path}: {e}")
|
|
209
|
+
if exit_on_error:
|
|
210
|
+
sys.exit(1)
|
|
211
|
+
raise
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def apply_style_overrides(
|
|
215
|
+
style_params: Dict[str, Any],
|
|
216
|
+
overrides: Dict[str, str],
|
|
217
|
+
logger: Optional[logging.Logger] = None,
|
|
218
|
+
) -> None:
|
|
219
|
+
"""
|
|
220
|
+
Recursively apply overrides to style parameters (in place).
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
style_params: Style parameters dict to modify.
|
|
224
|
+
overrides: Dict of "section.key" -> value overrides.
|
|
225
|
+
logger: Optional logger for messages.
|
|
226
|
+
"""
|
|
227
|
+
log = logger or logging.getLogger(__name__)
|
|
228
|
+
|
|
229
|
+
for key, value in overrides.items():
|
|
230
|
+
keys = key.split('.')
|
|
231
|
+
current_level = style_params
|
|
232
|
+
|
|
233
|
+
for i, k in enumerate(keys):
|
|
234
|
+
if i == len(keys) - 1:
|
|
235
|
+
if k in current_level:
|
|
236
|
+
# Cast to original type if possible
|
|
237
|
+
try:
|
|
238
|
+
original_type = type(current_level[k])
|
|
239
|
+
if original_type == bool:
|
|
240
|
+
value = str(value).lower() in ('true', '1', 't', 'y', 'yes')
|
|
241
|
+
elif current_level[k] is not None:
|
|
242
|
+
value = original_type(value)
|
|
243
|
+
except (ValueError, TypeError) as e:
|
|
244
|
+
log.warning(
|
|
245
|
+
f"Could not cast override value '{value}' for key '{key}' "
|
|
246
|
+
f"to original type. Using as string. Error: {e}"
|
|
247
|
+
)
|
|
248
|
+
current_level[k] = value
|
|
249
|
+
log.info(f"Overrode style: {key} = {value}")
|
|
250
|
+
else:
|
|
251
|
+
log.warning(f"Override key '{key}' not found in style parameters.")
|
|
252
|
+
elif k in current_level and isinstance(current_level[k], dict):
|
|
253
|
+
current_level = current_level[k]
|
|
254
|
+
else:
|
|
255
|
+
log.warning(
|
|
256
|
+
f"Override key part '{k}' not found or not a dictionary for key '{key}'."
|
|
257
|
+
)
|
|
258
|
+
break
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def update_asset_paths(
|
|
262
|
+
style_data: Dict[str, Any],
|
|
263
|
+
local_assets: Dict[str, str],
|
|
264
|
+
logger: Optional[logging.Logger] = None,
|
|
265
|
+
) -> bool:
|
|
266
|
+
"""
|
|
267
|
+
Update file paths in style data to point to local asset files.
|
|
268
|
+
|
|
269
|
+
This is used when assets are downloaded from GCS and need their
|
|
270
|
+
paths updated in the style JSON to point to the local copies.
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
style_data: Style parameters dict to modify (in place).
|
|
274
|
+
local_assets: Dict mapping asset keys to local file paths.
|
|
275
|
+
logger: Optional logger for messages.
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
True if any paths were updated, False otherwise.
|
|
279
|
+
"""
|
|
280
|
+
log = logger or logging.getLogger(__name__)
|
|
281
|
+
updates_made = False
|
|
282
|
+
|
|
283
|
+
for asset_key, local_path in local_assets.items():
|
|
284
|
+
if asset_key not in ASSET_KEY_MAPPINGS:
|
|
285
|
+
continue
|
|
286
|
+
|
|
287
|
+
mappings = ASSET_KEY_MAPPINGS[asset_key]
|
|
288
|
+
|
|
289
|
+
# Normalize to list of tuples
|
|
290
|
+
if isinstance(mappings, tuple):
|
|
291
|
+
mappings = [mappings]
|
|
292
|
+
|
|
293
|
+
for section, field in mappings:
|
|
294
|
+
if section in style_data and isinstance(style_data[section], dict):
|
|
295
|
+
old_value = style_data[section].get(field, 'NOT SET')
|
|
296
|
+
style_data[section][field] = local_path
|
|
297
|
+
log.info(f"Updated {section}.{field}: {old_value} -> {local_path}")
|
|
298
|
+
updates_made = True
|
|
299
|
+
|
|
300
|
+
return updates_made
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def save_style_params(
|
|
304
|
+
style_data: Dict[str, Any],
|
|
305
|
+
output_path: str,
|
|
306
|
+
logger: Optional[logging.Logger] = None,
|
|
307
|
+
) -> str:
|
|
308
|
+
"""
|
|
309
|
+
Save style parameters to a JSON file.
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
style_data: Style parameters dict to save.
|
|
313
|
+
output_path: Path to save the JSON file.
|
|
314
|
+
logger: Optional logger for messages.
|
|
315
|
+
|
|
316
|
+
Returns:
|
|
317
|
+
The output path.
|
|
318
|
+
"""
|
|
319
|
+
log = logger or logging.getLogger(__name__)
|
|
320
|
+
|
|
321
|
+
with open(output_path, 'w') as f:
|
|
322
|
+
json.dump(style_data, f, indent=2)
|
|
323
|
+
|
|
324
|
+
log.info(f"Saved style parameters to: {output_path}")
|
|
325
|
+
return output_path
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
# =============================================================================
|
|
329
|
+
# GCS STYLE LOADING (for backend workers)
|
|
330
|
+
# =============================================================================
|
|
331
|
+
|
|
332
|
+
def load_styles_from_gcs(
|
|
333
|
+
style_params_gcs_path: Optional[str],
|
|
334
|
+
style_assets: Optional[Dict[str, str]],
|
|
335
|
+
temp_dir: str,
|
|
336
|
+
download_func: Callable[[str, str], None],
|
|
337
|
+
logger: Optional[logging.Logger] = None,
|
|
338
|
+
) -> Tuple[str, Dict[str, Any]]:
|
|
339
|
+
"""
|
|
340
|
+
Download and process style configuration from GCS.
|
|
341
|
+
|
|
342
|
+
This is the main entry point for backend workers to load styles.
|
|
343
|
+
It handles:
|
|
344
|
+
1. Downloading style_params.json from GCS
|
|
345
|
+
2. Downloading all style assets (backgrounds, fonts)
|
|
346
|
+
3. Updating paths in the style JSON to point to local files
|
|
347
|
+
4. Saving the updated JSON
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
style_params_gcs_path: GCS path to style_params.json, or None.
|
|
351
|
+
style_assets: Dict of asset_key -> GCS path for style assets.
|
|
352
|
+
temp_dir: Temporary directory for downloaded files.
|
|
353
|
+
download_func: Function(gcs_path, local_path) to download files.
|
|
354
|
+
logger: Optional logger for messages.
|
|
355
|
+
|
|
356
|
+
Returns:
|
|
357
|
+
Tuple of (local_styles_path, style_data_dict).
|
|
358
|
+
If no custom styles, returns (minimal_styles_path, minimal_styles_dict).
|
|
359
|
+
"""
|
|
360
|
+
log = logger or logging.getLogger(__name__)
|
|
361
|
+
|
|
362
|
+
style_dir = os.path.join(temp_dir, "style")
|
|
363
|
+
os.makedirs(style_dir, exist_ok=True)
|
|
364
|
+
styles_path = os.path.join(style_dir, "styles.json")
|
|
365
|
+
|
|
366
|
+
if not style_params_gcs_path:
|
|
367
|
+
log.info("No custom style_params_gcs_path found, using minimal/default styles")
|
|
368
|
+
minimal_styles = get_minimal_karaoke_styles()
|
|
369
|
+
save_style_params(minimal_styles, styles_path, log)
|
|
370
|
+
return styles_path, minimal_styles
|
|
371
|
+
|
|
372
|
+
try:
|
|
373
|
+
log.info(f"Downloading custom styles from {style_params_gcs_path}")
|
|
374
|
+
download_func(style_params_gcs_path, styles_path)
|
|
375
|
+
|
|
376
|
+
with open(styles_path, 'r') as f:
|
|
377
|
+
style_data = json.load(f)
|
|
378
|
+
|
|
379
|
+
log.info(f"Loaded style sections: {list(style_data.keys())}")
|
|
380
|
+
|
|
381
|
+
# Download style assets
|
|
382
|
+
local_assets = {}
|
|
383
|
+
if style_assets:
|
|
384
|
+
log.info(f"Downloading {len(style_assets)} style assets...")
|
|
385
|
+
for asset_key, gcs_path in style_assets.items():
|
|
386
|
+
if asset_key == 'style_params':
|
|
387
|
+
continue # Already downloaded
|
|
388
|
+
try:
|
|
389
|
+
ext = os.path.splitext(gcs_path)[1] or '.png'
|
|
390
|
+
local_path = os.path.join(style_dir, f"{asset_key}{ext}")
|
|
391
|
+
download_func(gcs_path, local_path)
|
|
392
|
+
local_assets[asset_key] = local_path
|
|
393
|
+
log.info(f" Downloaded {asset_key}: {local_path}")
|
|
394
|
+
except Exception as e:
|
|
395
|
+
log.warning(f" Failed to download {asset_key}: {e}")
|
|
396
|
+
|
|
397
|
+
# Update paths in style_data
|
|
398
|
+
if local_assets:
|
|
399
|
+
updates_made = update_asset_paths(style_data, local_assets, log)
|
|
400
|
+
if updates_made:
|
|
401
|
+
save_style_params(style_data, styles_path, log)
|
|
402
|
+
|
|
403
|
+
# Log karaoke style for debugging
|
|
404
|
+
if 'karaoke' in style_data:
|
|
405
|
+
k = style_data['karaoke']
|
|
406
|
+
log.info(
|
|
407
|
+
f"Final karaoke style: background_image={k.get('background_image', 'NOT SET')}, "
|
|
408
|
+
f"font_path={k.get('font_path', 'NOT SET')}"
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
return styles_path, style_data
|
|
412
|
+
|
|
413
|
+
except Exception as e:
|
|
414
|
+
log.warning(f"Failed to download custom styles: {e}, using defaults")
|
|
415
|
+
minimal_styles = get_minimal_karaoke_styles()
|
|
416
|
+
save_style_params(minimal_styles, styles_path, log)
|
|
417
|
+
return styles_path, minimal_styles
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
# =============================================================================
|
|
421
|
+
# HELPER FUNCTIONS
|
|
422
|
+
# =============================================================================
|
|
423
|
+
|
|
424
|
+
def get_default_style_params() -> Dict[str, Any]:
|
|
425
|
+
"""Get a fresh copy of the default style parameters."""
|
|
426
|
+
return {
|
|
427
|
+
"intro": DEFAULT_INTRO_STYLE.copy(),
|
|
428
|
+
"end": DEFAULT_END_STYLE.copy(),
|
|
429
|
+
"karaoke": DEFAULT_KARAOKE_STYLE.copy(),
|
|
430
|
+
"cdg": DEFAULT_CDG_STYLE.copy(),
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
def get_minimal_karaoke_styles() -> Dict[str, Any]:
|
|
435
|
+
"""
|
|
436
|
+
Get minimal styles for karaoke video generation.
|
|
437
|
+
|
|
438
|
+
This is used when no custom styles are provided, providing
|
|
439
|
+
just enough configuration for the ASS subtitle generator and CDG generator.
|
|
440
|
+
"""
|
|
441
|
+
return {
|
|
442
|
+
"karaoke": DEFAULT_KARAOKE_STYLE.copy(),
|
|
443
|
+
"cdg": DEFAULT_CDG_STYLE.copy(),
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
def get_intro_format(style_params: Dict[str, Any]) -> Dict[str, Any]:
|
|
448
|
+
"""
|
|
449
|
+
Extract intro/title screen format from style parameters.
|
|
450
|
+
|
|
451
|
+
Merges custom intro params with defaults.
|
|
452
|
+
"""
|
|
453
|
+
defaults = DEFAULT_INTRO_STYLE
|
|
454
|
+
intro_params = style_params.get("intro", {})
|
|
455
|
+
|
|
456
|
+
result = defaults.copy()
|
|
457
|
+
result.update(intro_params)
|
|
458
|
+
return result
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def get_end_format(style_params: Dict[str, Any]) -> Dict[str, Any]:
|
|
462
|
+
"""
|
|
463
|
+
Extract end screen format from style parameters.
|
|
464
|
+
|
|
465
|
+
Merges custom end params with defaults.
|
|
466
|
+
"""
|
|
467
|
+
defaults = DEFAULT_END_STYLE
|
|
468
|
+
end_params = style_params.get("end", {})
|
|
469
|
+
|
|
470
|
+
result = defaults.copy()
|
|
471
|
+
result.update(end_params)
|
|
472
|
+
return result
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
def get_karaoke_format(style_params: Dict[str, Any]) -> Dict[str, Any]:
|
|
476
|
+
"""
|
|
477
|
+
Extract karaoke video format from style parameters.
|
|
478
|
+
|
|
479
|
+
Merges custom karaoke params with defaults.
|
|
480
|
+
"""
|
|
481
|
+
defaults = DEFAULT_KARAOKE_STYLE
|
|
482
|
+
karaoke_params = style_params.get("karaoke", {})
|
|
483
|
+
|
|
484
|
+
result = defaults.copy()
|
|
485
|
+
result.update(karaoke_params)
|
|
486
|
+
return result
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
def get_cdg_format(style_params: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
490
|
+
"""
|
|
491
|
+
Extract CDG generation format from style parameters.
|
|
492
|
+
|
|
493
|
+
Returns None if no CDG section is defined.
|
|
494
|
+
"""
|
|
495
|
+
if "cdg" not in style_params:
|
|
496
|
+
return None
|
|
497
|
+
|
|
498
|
+
defaults = DEFAULT_CDG_STYLE
|
|
499
|
+
cdg_params = style_params.get("cdg", {})
|
|
500
|
+
|
|
501
|
+
result = defaults.copy()
|
|
502
|
+
result.update(cdg_params)
|
|
503
|
+
return result
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
def get_video_durations(style_params: Dict[str, Any]) -> Tuple[int, int]:
|
|
507
|
+
"""
|
|
508
|
+
Get intro and end video durations from style parameters.
|
|
509
|
+
|
|
510
|
+
Returns:
|
|
511
|
+
Tuple of (intro_duration, end_duration) in seconds.
|
|
512
|
+
"""
|
|
513
|
+
intro_duration = style_params.get("intro", {}).get(
|
|
514
|
+
"video_duration", DEFAULT_INTRO_STYLE["video_duration"]
|
|
515
|
+
)
|
|
516
|
+
end_duration = style_params.get("end", {}).get(
|
|
517
|
+
"video_duration", DEFAULT_END_STYLE["video_duration"]
|
|
518
|
+
)
|
|
519
|
+
return intro_duration, end_duration
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
def get_existing_images(style_params: Dict[str, Any]) -> Tuple[Optional[str], Optional[str]]:
|
|
523
|
+
"""
|
|
524
|
+
Get existing title and end images from style parameters.
|
|
525
|
+
|
|
526
|
+
Returns:
|
|
527
|
+
Tuple of (existing_title_image, existing_end_image) paths or None.
|
|
528
|
+
"""
|
|
529
|
+
existing_title_image = style_params.get("intro", {}).get("existing_image")
|
|
530
|
+
existing_end_image = style_params.get("end", {}).get("existing_image")
|
|
531
|
+
return existing_title_image, existing_end_image
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
def sanitize_filename(filename):
|
|
4
|
+
"""Replace or remove characters that are unsafe for filenames."""
|
|
5
|
+
if filename is None:
|
|
6
|
+
return None
|
|
7
|
+
# Replace problematic characters with underscores
|
|
8
|
+
for char in ["\\", "/", ":", "*", "?", '"', "<", ">", "|"]:
|
|
9
|
+
filename = filename.replace(char, "_")
|
|
10
|
+
# Remove any trailing periods or spaces
|
|
11
|
+
filename = filename.rstrip(". ") # Added period here as well
|
|
12
|
+
# Remove any leading periods or spaces
|
|
13
|
+
filename = filename.lstrip(". ")
|
|
14
|
+
# Replace multiple underscores with a single one
|
|
15
|
+
filename = re.sub(r'_+', '_', filename)
|
|
16
|
+
# Replace multiple spaces with a single one
|
|
17
|
+
filename = re.sub(r' +', ' ', filename)
|
|
18
|
+
return filename
|