karaoke-gen 0.57.0__py3-none-any.whl → 0.71.27__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 (268) hide show
  1. karaoke_gen/audio_fetcher.py +461 -0
  2. karaoke_gen/audio_processor.py +407 -30
  3. karaoke_gen/config.py +62 -113
  4. karaoke_gen/file_handler.py +32 -59
  5. karaoke_gen/karaoke_finalise/karaoke_finalise.py +148 -67
  6. karaoke_gen/karaoke_gen.py +270 -61
  7. karaoke_gen/lyrics_processor.py +13 -1
  8. karaoke_gen/metadata.py +78 -73
  9. karaoke_gen/pipeline/__init__.py +87 -0
  10. karaoke_gen/pipeline/base.py +215 -0
  11. karaoke_gen/pipeline/context.py +230 -0
  12. karaoke_gen/pipeline/executors/__init__.py +21 -0
  13. karaoke_gen/pipeline/executors/local.py +159 -0
  14. karaoke_gen/pipeline/executors/remote.py +257 -0
  15. karaoke_gen/pipeline/stages/__init__.py +27 -0
  16. karaoke_gen/pipeline/stages/finalize.py +202 -0
  17. karaoke_gen/pipeline/stages/render.py +165 -0
  18. karaoke_gen/pipeline/stages/screens.py +139 -0
  19. karaoke_gen/pipeline/stages/separation.py +191 -0
  20. karaoke_gen/pipeline/stages/transcription.py +191 -0
  21. karaoke_gen/style_loader.py +531 -0
  22. karaoke_gen/utils/bulk_cli.py +6 -0
  23. karaoke_gen/utils/cli_args.py +424 -0
  24. karaoke_gen/utils/gen_cli.py +26 -261
  25. karaoke_gen/utils/remote_cli.py +1965 -0
  26. karaoke_gen/video_background_processor.py +351 -0
  27. karaoke_gen-0.71.27.dist-info/METADATA +610 -0
  28. karaoke_gen-0.71.27.dist-info/RECORD +275 -0
  29. {karaoke_gen-0.57.0.dist-info → karaoke_gen-0.71.27.dist-info}/WHEEL +1 -1
  30. {karaoke_gen-0.57.0.dist-info → karaoke_gen-0.71.27.dist-info}/entry_points.txt +1 -0
  31. lyrics_transcriber/__init__.py +10 -0
  32. lyrics_transcriber/cli/__init__.py +0 -0
  33. lyrics_transcriber/cli/cli_main.py +285 -0
  34. lyrics_transcriber/core/__init__.py +0 -0
  35. lyrics_transcriber/core/config.py +50 -0
  36. lyrics_transcriber/core/controller.py +520 -0
  37. lyrics_transcriber/correction/__init__.py +0 -0
  38. lyrics_transcriber/correction/agentic/__init__.py +9 -0
  39. lyrics_transcriber/correction/agentic/adapter.py +71 -0
  40. lyrics_transcriber/correction/agentic/agent.py +313 -0
  41. lyrics_transcriber/correction/agentic/feedback/aggregator.py +12 -0
  42. lyrics_transcriber/correction/agentic/feedback/collector.py +17 -0
  43. lyrics_transcriber/correction/agentic/feedback/retention.py +24 -0
  44. lyrics_transcriber/correction/agentic/feedback/store.py +76 -0
  45. lyrics_transcriber/correction/agentic/handlers/__init__.py +24 -0
  46. lyrics_transcriber/correction/agentic/handlers/ambiguous.py +44 -0
  47. lyrics_transcriber/correction/agentic/handlers/background_vocals.py +68 -0
  48. lyrics_transcriber/correction/agentic/handlers/base.py +51 -0
  49. lyrics_transcriber/correction/agentic/handlers/complex_multi_error.py +46 -0
  50. lyrics_transcriber/correction/agentic/handlers/extra_words.py +74 -0
  51. lyrics_transcriber/correction/agentic/handlers/no_error.py +42 -0
  52. lyrics_transcriber/correction/agentic/handlers/punctuation.py +44 -0
  53. lyrics_transcriber/correction/agentic/handlers/registry.py +60 -0
  54. lyrics_transcriber/correction/agentic/handlers/repeated_section.py +44 -0
  55. lyrics_transcriber/correction/agentic/handlers/sound_alike.py +126 -0
  56. lyrics_transcriber/correction/agentic/models/__init__.py +5 -0
  57. lyrics_transcriber/correction/agentic/models/ai_correction.py +31 -0
  58. lyrics_transcriber/correction/agentic/models/correction_session.py +30 -0
  59. lyrics_transcriber/correction/agentic/models/enums.py +38 -0
  60. lyrics_transcriber/correction/agentic/models/human_feedback.py +30 -0
  61. lyrics_transcriber/correction/agentic/models/learning_data.py +26 -0
  62. lyrics_transcriber/correction/agentic/models/observability_metrics.py +28 -0
  63. lyrics_transcriber/correction/agentic/models/schemas.py +46 -0
  64. lyrics_transcriber/correction/agentic/models/utils.py +19 -0
  65. lyrics_transcriber/correction/agentic/observability/__init__.py +5 -0
  66. lyrics_transcriber/correction/agentic/observability/langfuse_integration.py +35 -0
  67. lyrics_transcriber/correction/agentic/observability/metrics.py +46 -0
  68. lyrics_transcriber/correction/agentic/observability/performance.py +19 -0
  69. lyrics_transcriber/correction/agentic/prompts/__init__.py +2 -0
  70. lyrics_transcriber/correction/agentic/prompts/classifier.py +227 -0
  71. lyrics_transcriber/correction/agentic/providers/__init__.py +6 -0
  72. lyrics_transcriber/correction/agentic/providers/base.py +36 -0
  73. lyrics_transcriber/correction/agentic/providers/circuit_breaker.py +145 -0
  74. lyrics_transcriber/correction/agentic/providers/config.py +73 -0
  75. lyrics_transcriber/correction/agentic/providers/constants.py +24 -0
  76. lyrics_transcriber/correction/agentic/providers/health.py +28 -0
  77. lyrics_transcriber/correction/agentic/providers/langchain_bridge.py +212 -0
  78. lyrics_transcriber/correction/agentic/providers/model_factory.py +209 -0
  79. lyrics_transcriber/correction/agentic/providers/response_cache.py +218 -0
  80. lyrics_transcriber/correction/agentic/providers/response_parser.py +111 -0
  81. lyrics_transcriber/correction/agentic/providers/retry_executor.py +127 -0
  82. lyrics_transcriber/correction/agentic/router.py +35 -0
  83. lyrics_transcriber/correction/agentic/workflows/__init__.py +5 -0
  84. lyrics_transcriber/correction/agentic/workflows/consensus_workflow.py +24 -0
  85. lyrics_transcriber/correction/agentic/workflows/correction_graph.py +59 -0
  86. lyrics_transcriber/correction/agentic/workflows/feedback_workflow.py +24 -0
  87. lyrics_transcriber/correction/anchor_sequence.py +1043 -0
  88. lyrics_transcriber/correction/corrector.py +760 -0
  89. lyrics_transcriber/correction/feedback/__init__.py +2 -0
  90. lyrics_transcriber/correction/feedback/schemas.py +107 -0
  91. lyrics_transcriber/correction/feedback/store.py +236 -0
  92. lyrics_transcriber/correction/handlers/__init__.py +0 -0
  93. lyrics_transcriber/correction/handlers/base.py +52 -0
  94. lyrics_transcriber/correction/handlers/extend_anchor.py +149 -0
  95. lyrics_transcriber/correction/handlers/levenshtein.py +189 -0
  96. lyrics_transcriber/correction/handlers/llm.py +293 -0
  97. lyrics_transcriber/correction/handlers/llm_providers.py +60 -0
  98. lyrics_transcriber/correction/handlers/no_space_punct_match.py +154 -0
  99. lyrics_transcriber/correction/handlers/relaxed_word_count_match.py +85 -0
  100. lyrics_transcriber/correction/handlers/repeat.py +88 -0
  101. lyrics_transcriber/correction/handlers/sound_alike.py +259 -0
  102. lyrics_transcriber/correction/handlers/syllables_match.py +252 -0
  103. lyrics_transcriber/correction/handlers/word_count_match.py +80 -0
  104. lyrics_transcriber/correction/handlers/word_operations.py +187 -0
  105. lyrics_transcriber/correction/operations.py +352 -0
  106. lyrics_transcriber/correction/phrase_analyzer.py +435 -0
  107. lyrics_transcriber/correction/text_utils.py +30 -0
  108. lyrics_transcriber/frontend/.gitignore +23 -0
  109. lyrics_transcriber/frontend/.yarn/releases/yarn-4.7.0.cjs +935 -0
  110. lyrics_transcriber/frontend/.yarnrc.yml +3 -0
  111. lyrics_transcriber/frontend/README.md +50 -0
  112. lyrics_transcriber/frontend/REPLACE_ALL_FUNCTIONALITY.md +210 -0
  113. lyrics_transcriber/frontend/__init__.py +25 -0
  114. lyrics_transcriber/frontend/eslint.config.js +28 -0
  115. lyrics_transcriber/frontend/index.html +18 -0
  116. lyrics_transcriber/frontend/package.json +42 -0
  117. lyrics_transcriber/frontend/public/android-chrome-192x192.png +0 -0
  118. lyrics_transcriber/frontend/public/android-chrome-512x512.png +0 -0
  119. lyrics_transcriber/frontend/public/apple-touch-icon.png +0 -0
  120. lyrics_transcriber/frontend/public/favicon-16x16.png +0 -0
  121. lyrics_transcriber/frontend/public/favicon-32x32.png +0 -0
  122. lyrics_transcriber/frontend/public/favicon.ico +0 -0
  123. lyrics_transcriber/frontend/public/nomad-karaoke-logo.png +0 -0
  124. lyrics_transcriber/frontend/src/App.tsx +212 -0
  125. lyrics_transcriber/frontend/src/api.ts +239 -0
  126. lyrics_transcriber/frontend/src/components/AIFeedbackModal.tsx +77 -0
  127. lyrics_transcriber/frontend/src/components/AddLyricsModal.tsx +114 -0
  128. lyrics_transcriber/frontend/src/components/AgenticCorrectionMetrics.tsx +204 -0
  129. lyrics_transcriber/frontend/src/components/AudioPlayer.tsx +180 -0
  130. lyrics_transcriber/frontend/src/components/CorrectedWordWithActions.tsx +167 -0
  131. lyrics_transcriber/frontend/src/components/CorrectionAnnotationModal.tsx +359 -0
  132. lyrics_transcriber/frontend/src/components/CorrectionDetailCard.tsx +281 -0
  133. lyrics_transcriber/frontend/src/components/CorrectionMetrics.tsx +162 -0
  134. lyrics_transcriber/frontend/src/components/DurationTimelineView.tsx +257 -0
  135. lyrics_transcriber/frontend/src/components/EditActionBar.tsx +68 -0
  136. lyrics_transcriber/frontend/src/components/EditModal.tsx +702 -0
  137. lyrics_transcriber/frontend/src/components/EditTimelineSection.tsx +496 -0
  138. lyrics_transcriber/frontend/src/components/EditWordList.tsx +379 -0
  139. lyrics_transcriber/frontend/src/components/FileUpload.tsx +77 -0
  140. lyrics_transcriber/frontend/src/components/FindReplaceModal.tsx +467 -0
  141. lyrics_transcriber/frontend/src/components/Header.tsx +387 -0
  142. lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +1373 -0
  143. lyrics_transcriber/frontend/src/components/MetricsDashboard.tsx +51 -0
  144. lyrics_transcriber/frontend/src/components/ModeSelector.tsx +67 -0
  145. lyrics_transcriber/frontend/src/components/ModelSelector.tsx +23 -0
  146. lyrics_transcriber/frontend/src/components/PreviewVideoSection.tsx +144 -0
  147. lyrics_transcriber/frontend/src/components/ReferenceView.tsx +268 -0
  148. lyrics_transcriber/frontend/src/components/ReplaceAllLyricsModal.tsx +688 -0
  149. lyrics_transcriber/frontend/src/components/ReviewChangesModal.tsx +354 -0
  150. lyrics_transcriber/frontend/src/components/SegmentDetailsModal.tsx +64 -0
  151. lyrics_transcriber/frontend/src/components/TimelineEditor.tsx +376 -0
  152. lyrics_transcriber/frontend/src/components/TimingOffsetModal.tsx +131 -0
  153. lyrics_transcriber/frontend/src/components/TranscriptionView.tsx +256 -0
  154. lyrics_transcriber/frontend/src/components/WordDivider.tsx +187 -0
  155. lyrics_transcriber/frontend/src/components/shared/components/HighlightedText.tsx +379 -0
  156. lyrics_transcriber/frontend/src/components/shared/components/SourceSelector.tsx +56 -0
  157. lyrics_transcriber/frontend/src/components/shared/components/Word.tsx +87 -0
  158. lyrics_transcriber/frontend/src/components/shared/constants.ts +20 -0
  159. lyrics_transcriber/frontend/src/components/shared/hooks/useWordClick.ts +180 -0
  160. lyrics_transcriber/frontend/src/components/shared/styles.ts +13 -0
  161. lyrics_transcriber/frontend/src/components/shared/types.js +2 -0
  162. lyrics_transcriber/frontend/src/components/shared/types.ts +129 -0
  163. lyrics_transcriber/frontend/src/components/shared/utils/keyboardHandlers.ts +177 -0
  164. lyrics_transcriber/frontend/src/components/shared/utils/localStorage.ts +78 -0
  165. lyrics_transcriber/frontend/src/components/shared/utils/referenceLineCalculator.ts +75 -0
  166. lyrics_transcriber/frontend/src/components/shared/utils/segmentOperations.ts +360 -0
  167. lyrics_transcriber/frontend/src/components/shared/utils/timingUtils.ts +110 -0
  168. lyrics_transcriber/frontend/src/components/shared/utils/wordUtils.ts +22 -0
  169. lyrics_transcriber/frontend/src/hooks/useManualSync.ts +435 -0
  170. lyrics_transcriber/frontend/src/main.tsx +17 -0
  171. lyrics_transcriber/frontend/src/theme.ts +177 -0
  172. lyrics_transcriber/frontend/src/types/global.d.ts +9 -0
  173. lyrics_transcriber/frontend/src/types.js +2 -0
  174. lyrics_transcriber/frontend/src/types.ts +199 -0
  175. lyrics_transcriber/frontend/src/validation.ts +132 -0
  176. lyrics_transcriber/frontend/src/vite-env.d.ts +1 -0
  177. lyrics_transcriber/frontend/tsconfig.app.json +26 -0
  178. lyrics_transcriber/frontend/tsconfig.json +25 -0
  179. lyrics_transcriber/frontend/tsconfig.node.json +23 -0
  180. lyrics_transcriber/frontend/tsconfig.tsbuildinfo +1 -0
  181. lyrics_transcriber/frontend/update_version.js +11 -0
  182. lyrics_transcriber/frontend/vite.config.d.ts +2 -0
  183. lyrics_transcriber/frontend/vite.config.js +10 -0
  184. lyrics_transcriber/frontend/vite.config.ts +11 -0
  185. lyrics_transcriber/frontend/web_assets/android-chrome-192x192.png +0 -0
  186. lyrics_transcriber/frontend/web_assets/android-chrome-512x512.png +0 -0
  187. lyrics_transcriber/frontend/web_assets/apple-touch-icon.png +0 -0
  188. lyrics_transcriber/frontend/web_assets/assets/index-DdJTDWH3.js +42039 -0
  189. lyrics_transcriber/frontend/web_assets/assets/index-DdJTDWH3.js.map +1 -0
  190. lyrics_transcriber/frontend/web_assets/favicon-16x16.png +0 -0
  191. lyrics_transcriber/frontend/web_assets/favicon-32x32.png +0 -0
  192. lyrics_transcriber/frontend/web_assets/favicon.ico +0 -0
  193. lyrics_transcriber/frontend/web_assets/index.html +18 -0
  194. lyrics_transcriber/frontend/web_assets/nomad-karaoke-logo.png +0 -0
  195. lyrics_transcriber/frontend/yarn.lock +3752 -0
  196. lyrics_transcriber/lyrics/__init__.py +0 -0
  197. lyrics_transcriber/lyrics/base_lyrics_provider.py +211 -0
  198. lyrics_transcriber/lyrics/file_provider.py +95 -0
  199. lyrics_transcriber/lyrics/genius.py +384 -0
  200. lyrics_transcriber/lyrics/lrclib.py +231 -0
  201. lyrics_transcriber/lyrics/musixmatch.py +156 -0
  202. lyrics_transcriber/lyrics/spotify.py +290 -0
  203. lyrics_transcriber/lyrics/user_input_provider.py +44 -0
  204. lyrics_transcriber/output/__init__.py +0 -0
  205. lyrics_transcriber/output/ass/__init__.py +21 -0
  206. lyrics_transcriber/output/ass/ass.py +2088 -0
  207. lyrics_transcriber/output/ass/ass_specs.txt +732 -0
  208. lyrics_transcriber/output/ass/config.py +180 -0
  209. lyrics_transcriber/output/ass/constants.py +23 -0
  210. lyrics_transcriber/output/ass/event.py +94 -0
  211. lyrics_transcriber/output/ass/formatters.py +132 -0
  212. lyrics_transcriber/output/ass/lyrics_line.py +265 -0
  213. lyrics_transcriber/output/ass/lyrics_screen.py +252 -0
  214. lyrics_transcriber/output/ass/section_detector.py +89 -0
  215. lyrics_transcriber/output/ass/section_screen.py +106 -0
  216. lyrics_transcriber/output/ass/style.py +187 -0
  217. lyrics_transcriber/output/cdg.py +619 -0
  218. lyrics_transcriber/output/cdgmaker/__init__.py +0 -0
  219. lyrics_transcriber/output/cdgmaker/cdg.py +262 -0
  220. lyrics_transcriber/output/cdgmaker/composer.py +2260 -0
  221. lyrics_transcriber/output/cdgmaker/config.py +151 -0
  222. lyrics_transcriber/output/cdgmaker/images/instrumental.png +0 -0
  223. lyrics_transcriber/output/cdgmaker/images/intro.png +0 -0
  224. lyrics_transcriber/output/cdgmaker/pack.py +507 -0
  225. lyrics_transcriber/output/cdgmaker/render.py +346 -0
  226. lyrics_transcriber/output/cdgmaker/transitions/centertexttoplogobottomtext.png +0 -0
  227. lyrics_transcriber/output/cdgmaker/transitions/circlein.png +0 -0
  228. lyrics_transcriber/output/cdgmaker/transitions/circleout.png +0 -0
  229. lyrics_transcriber/output/cdgmaker/transitions/fizzle.png +0 -0
  230. lyrics_transcriber/output/cdgmaker/transitions/largecentertexttoplogo.png +0 -0
  231. lyrics_transcriber/output/cdgmaker/transitions/rectangle.png +0 -0
  232. lyrics_transcriber/output/cdgmaker/transitions/spiral.png +0 -0
  233. lyrics_transcriber/output/cdgmaker/transitions/topleftmusicalnotes.png +0 -0
  234. lyrics_transcriber/output/cdgmaker/transitions/wipein.png +0 -0
  235. lyrics_transcriber/output/cdgmaker/transitions/wipeleft.png +0 -0
  236. lyrics_transcriber/output/cdgmaker/transitions/wipeout.png +0 -0
  237. lyrics_transcriber/output/cdgmaker/transitions/wiperight.png +0 -0
  238. lyrics_transcriber/output/cdgmaker/utils.py +132 -0
  239. lyrics_transcriber/output/countdown_processor.py +267 -0
  240. lyrics_transcriber/output/fonts/AvenirNext-Bold.ttf +0 -0
  241. lyrics_transcriber/output/fonts/DMSans-VariableFont_opsz,wght.ttf +0 -0
  242. lyrics_transcriber/output/fonts/DMSerifDisplay-Regular.ttf +0 -0
  243. lyrics_transcriber/output/fonts/Oswald-SemiBold.ttf +0 -0
  244. lyrics_transcriber/output/fonts/Zurich_Cn_BT_Bold.ttf +0 -0
  245. lyrics_transcriber/output/fonts/arial.ttf +0 -0
  246. lyrics_transcriber/output/fonts/georgia.ttf +0 -0
  247. lyrics_transcriber/output/fonts/verdana.ttf +0 -0
  248. lyrics_transcriber/output/generator.py +257 -0
  249. lyrics_transcriber/output/lrc_to_cdg.py +61 -0
  250. lyrics_transcriber/output/lyrics_file.py +102 -0
  251. lyrics_transcriber/output/plain_text.py +96 -0
  252. lyrics_transcriber/output/segment_resizer.py +431 -0
  253. lyrics_transcriber/output/subtitles.py +397 -0
  254. lyrics_transcriber/output/video.py +544 -0
  255. lyrics_transcriber/review/__init__.py +0 -0
  256. lyrics_transcriber/review/server.py +676 -0
  257. lyrics_transcriber/storage/__init__.py +0 -0
  258. lyrics_transcriber/storage/dropbox.py +225 -0
  259. lyrics_transcriber/transcribers/__init__.py +0 -0
  260. lyrics_transcriber/transcribers/audioshake.py +290 -0
  261. lyrics_transcriber/transcribers/base_transcriber.py +157 -0
  262. lyrics_transcriber/transcribers/whisper.py +330 -0
  263. lyrics_transcriber/types.py +648 -0
  264. lyrics_transcriber/utils/__init__.py +0 -0
  265. lyrics_transcriber/utils/word_utils.py +27 -0
  266. karaoke_gen-0.57.0.dist-info/METADATA +0 -167
  267. karaoke_gen-0.57.0.dist-info/RECORD +0 -23
  268. {karaoke_gen-0.57.0.dist-info → karaoke_gen-0.71.27.dist-info/licenses}/LICENSE +0 -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
@@ -1,4 +1,10 @@
1
1
  #!/usr/bin/env python
2
+ # Suppress SyntaxWarnings from third-party dependencies (pydub, syrics)
3
+ # that have invalid escape sequences in regex patterns (not yet fixed for Python 3.12+)
4
+ import warnings
5
+ warnings.filterwarnings("ignore", category=SyntaxWarning, module="pydub")
6
+ warnings.filterwarnings("ignore", category=SyntaxWarning, module="syrics")
7
+
2
8
  import argparse
3
9
  import logging
4
10
  import pkg_resources