karaoke-gen 0.90.1__py3-none-any.whl → 0.99.3__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 (197) hide show
  1. backend/.coveragerc +20 -0
  2. backend/.gitignore +37 -0
  3. backend/Dockerfile +43 -0
  4. backend/Dockerfile.base +74 -0
  5. backend/README.md +242 -0
  6. backend/__init__.py +0 -0
  7. backend/api/__init__.py +0 -0
  8. backend/api/dependencies.py +457 -0
  9. backend/api/routes/__init__.py +0 -0
  10. backend/api/routes/admin.py +835 -0
  11. backend/api/routes/audio_search.py +913 -0
  12. backend/api/routes/auth.py +348 -0
  13. backend/api/routes/file_upload.py +2112 -0
  14. backend/api/routes/health.py +409 -0
  15. backend/api/routes/internal.py +435 -0
  16. backend/api/routes/jobs.py +1629 -0
  17. backend/api/routes/review.py +652 -0
  18. backend/api/routes/themes.py +162 -0
  19. backend/api/routes/users.py +1513 -0
  20. backend/config.py +172 -0
  21. backend/main.py +157 -0
  22. backend/middleware/__init__.py +5 -0
  23. backend/middleware/audit_logging.py +124 -0
  24. backend/models/__init__.py +0 -0
  25. backend/models/job.py +519 -0
  26. backend/models/requests.py +123 -0
  27. backend/models/theme.py +153 -0
  28. backend/models/user.py +254 -0
  29. backend/models/worker_log.py +164 -0
  30. backend/pyproject.toml +29 -0
  31. backend/quick-check.sh +93 -0
  32. backend/requirements.txt +29 -0
  33. backend/run_tests.sh +60 -0
  34. backend/services/__init__.py +0 -0
  35. backend/services/audio_analysis_service.py +243 -0
  36. backend/services/audio_editing_service.py +278 -0
  37. backend/services/audio_search_service.py +702 -0
  38. backend/services/auth_service.py +630 -0
  39. backend/services/credential_manager.py +792 -0
  40. backend/services/discord_service.py +172 -0
  41. backend/services/dropbox_service.py +301 -0
  42. backend/services/email_service.py +1093 -0
  43. backend/services/encoding_interface.py +454 -0
  44. backend/services/encoding_service.py +502 -0
  45. backend/services/firestore_service.py +512 -0
  46. backend/services/flacfetch_client.py +573 -0
  47. backend/services/gce_encoding/README.md +72 -0
  48. backend/services/gce_encoding/__init__.py +22 -0
  49. backend/services/gce_encoding/main.py +589 -0
  50. backend/services/gce_encoding/requirements.txt +16 -0
  51. backend/services/gdrive_service.py +356 -0
  52. backend/services/job_logging.py +258 -0
  53. backend/services/job_manager.py +853 -0
  54. backend/services/job_notification_service.py +271 -0
  55. backend/services/langfuse_preloader.py +98 -0
  56. backend/services/local_encoding_service.py +590 -0
  57. backend/services/local_preview_encoding_service.py +407 -0
  58. backend/services/lyrics_cache_service.py +216 -0
  59. backend/services/metrics.py +413 -0
  60. backend/services/nltk_preloader.py +122 -0
  61. backend/services/packaging_service.py +287 -0
  62. backend/services/rclone_service.py +106 -0
  63. backend/services/spacy_preloader.py +65 -0
  64. backend/services/storage_service.py +209 -0
  65. backend/services/stripe_service.py +371 -0
  66. backend/services/structured_logging.py +254 -0
  67. backend/services/template_service.py +330 -0
  68. backend/services/theme_service.py +469 -0
  69. backend/services/tracing.py +543 -0
  70. backend/services/user_service.py +721 -0
  71. backend/services/worker_service.py +558 -0
  72. backend/services/youtube_service.py +112 -0
  73. backend/services/youtube_upload_service.py +445 -0
  74. backend/tests/__init__.py +4 -0
  75. backend/tests/conftest.py +224 -0
  76. backend/tests/emulator/__init__.py +7 -0
  77. backend/tests/emulator/conftest.py +109 -0
  78. backend/tests/emulator/test_e2e_cli_backend.py +1053 -0
  79. backend/tests/emulator/test_emulator_integration.py +356 -0
  80. backend/tests/emulator/test_style_loading_direct.py +436 -0
  81. backend/tests/emulator/test_worker_logs_direct.py +229 -0
  82. backend/tests/emulator/test_worker_logs_subcollection.py +443 -0
  83. backend/tests/requirements-test.txt +10 -0
  84. backend/tests/requirements.txt +6 -0
  85. backend/tests/test_admin_email_endpoints.py +411 -0
  86. backend/tests/test_api_integration.py +460 -0
  87. backend/tests/test_api_routes.py +93 -0
  88. backend/tests/test_audio_analysis_service.py +294 -0
  89. backend/tests/test_audio_editing_service.py +386 -0
  90. backend/tests/test_audio_search.py +1398 -0
  91. backend/tests/test_audio_services.py +378 -0
  92. backend/tests/test_auth_firestore.py +231 -0
  93. backend/tests/test_config_extended.py +68 -0
  94. backend/tests/test_credential_manager.py +377 -0
  95. backend/tests/test_dependencies.py +54 -0
  96. backend/tests/test_discord_service.py +244 -0
  97. backend/tests/test_distribution_services.py +820 -0
  98. backend/tests/test_dropbox_service.py +472 -0
  99. backend/tests/test_email_service.py +492 -0
  100. backend/tests/test_emulator_integration.py +322 -0
  101. backend/tests/test_encoding_interface.py +412 -0
  102. backend/tests/test_file_upload.py +1739 -0
  103. backend/tests/test_flacfetch_client.py +632 -0
  104. backend/tests/test_gdrive_service.py +524 -0
  105. backend/tests/test_instrumental_api.py +431 -0
  106. backend/tests/test_internal_api.py +343 -0
  107. backend/tests/test_job_creation_regression.py +583 -0
  108. backend/tests/test_job_manager.py +356 -0
  109. backend/tests/test_job_manager_notifications.py +329 -0
  110. backend/tests/test_job_notification_service.py +443 -0
  111. backend/tests/test_jobs_api.py +283 -0
  112. backend/tests/test_local_encoding_service.py +423 -0
  113. backend/tests/test_local_preview_encoding_service.py +567 -0
  114. backend/tests/test_main.py +87 -0
  115. backend/tests/test_models.py +918 -0
  116. backend/tests/test_packaging_service.py +382 -0
  117. backend/tests/test_requests.py +201 -0
  118. backend/tests/test_routes_jobs.py +282 -0
  119. backend/tests/test_routes_review.py +337 -0
  120. backend/tests/test_services.py +556 -0
  121. backend/tests/test_services_extended.py +112 -0
  122. backend/tests/test_spacy_preloader.py +119 -0
  123. backend/tests/test_storage_service.py +448 -0
  124. backend/tests/test_style_upload.py +261 -0
  125. backend/tests/test_template_service.py +295 -0
  126. backend/tests/test_theme_service.py +516 -0
  127. backend/tests/test_unicode_sanitization.py +522 -0
  128. backend/tests/test_upload_api.py +256 -0
  129. backend/tests/test_validate.py +156 -0
  130. backend/tests/test_video_worker_orchestrator.py +847 -0
  131. backend/tests/test_worker_log_subcollection.py +509 -0
  132. backend/tests/test_worker_logging.py +365 -0
  133. backend/tests/test_workers.py +1116 -0
  134. backend/tests/test_workers_extended.py +178 -0
  135. backend/tests/test_youtube_service.py +247 -0
  136. backend/tests/test_youtube_upload_service.py +568 -0
  137. backend/utils/test_data.py +27 -0
  138. backend/validate.py +173 -0
  139. backend/version.py +27 -0
  140. backend/workers/README.md +597 -0
  141. backend/workers/__init__.py +11 -0
  142. backend/workers/audio_worker.py +618 -0
  143. backend/workers/lyrics_worker.py +683 -0
  144. backend/workers/render_video_worker.py +483 -0
  145. backend/workers/screens_worker.py +535 -0
  146. backend/workers/style_helper.py +198 -0
  147. backend/workers/video_worker.py +1277 -0
  148. backend/workers/video_worker_orchestrator.py +701 -0
  149. backend/workers/worker_logging.py +278 -0
  150. karaoke_gen/instrumental_review/static/index.html +7 -4
  151. karaoke_gen/karaoke_finalise/karaoke_finalise.py +6 -1
  152. karaoke_gen/utils/__init__.py +163 -8
  153. karaoke_gen/video_background_processor.py +9 -4
  154. {karaoke_gen-0.90.1.dist-info → karaoke_gen-0.99.3.dist-info}/METADATA +1 -1
  155. {karaoke_gen-0.90.1.dist-info → karaoke_gen-0.99.3.dist-info}/RECORD +196 -46
  156. lyrics_transcriber/correction/agentic/agent.py +17 -6
  157. lyrics_transcriber/correction/agentic/providers/config.py +9 -5
  158. lyrics_transcriber/correction/agentic/providers/langchain_bridge.py +96 -93
  159. lyrics_transcriber/correction/agentic/providers/model_factory.py +27 -6
  160. lyrics_transcriber/correction/anchor_sequence.py +151 -37
  161. lyrics_transcriber/correction/corrector.py +192 -130
  162. lyrics_transcriber/correction/handlers/syllables_match.py +44 -2
  163. lyrics_transcriber/correction/operations.py +24 -9
  164. lyrics_transcriber/correction/phrase_analyzer.py +18 -0
  165. lyrics_transcriber/frontend/package-lock.json +2 -2
  166. lyrics_transcriber/frontend/package.json +1 -1
  167. lyrics_transcriber/frontend/src/components/AIFeedbackModal.tsx +1 -1
  168. lyrics_transcriber/frontend/src/components/CorrectedWordWithActions.tsx +11 -7
  169. lyrics_transcriber/frontend/src/components/EditActionBar.tsx +31 -5
  170. lyrics_transcriber/frontend/src/components/EditModal.tsx +28 -10
  171. lyrics_transcriber/frontend/src/components/EditTimelineSection.tsx +123 -27
  172. lyrics_transcriber/frontend/src/components/EditWordList.tsx +112 -60
  173. lyrics_transcriber/frontend/src/components/Header.tsx +90 -76
  174. lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +53 -31
  175. lyrics_transcriber/frontend/src/components/LyricsSynchronizer/SyncControls.tsx +44 -13
  176. lyrics_transcriber/frontend/src/components/LyricsSynchronizer/TimelineCanvas.tsx +66 -50
  177. lyrics_transcriber/frontend/src/components/LyricsSynchronizer/index.tsx +124 -30
  178. lyrics_transcriber/frontend/src/components/ReferenceView.tsx +1 -1
  179. lyrics_transcriber/frontend/src/components/TimelineEditor.tsx +12 -5
  180. lyrics_transcriber/frontend/src/components/TimingOffsetModal.tsx +3 -3
  181. lyrics_transcriber/frontend/src/components/TranscriptionView.tsx +1 -1
  182. lyrics_transcriber/frontend/src/components/WordDivider.tsx +11 -7
  183. lyrics_transcriber/frontend/src/components/shared/components/Word.tsx +4 -2
  184. lyrics_transcriber/frontend/src/hooks/useManualSync.ts +103 -1
  185. lyrics_transcriber/frontend/src/theme.ts +42 -15
  186. lyrics_transcriber/frontend/tsconfig.tsbuildinfo +1 -1
  187. lyrics_transcriber/frontend/vite.config.js +5 -0
  188. lyrics_transcriber/frontend/web_assets/assets/{index-BECn1o8Q.js → index-BSMgOq4Z.js} +6959 -5782
  189. lyrics_transcriber/frontend/web_assets/assets/index-BSMgOq4Z.js.map +1 -0
  190. lyrics_transcriber/frontend/web_assets/index.html +6 -2
  191. lyrics_transcriber/frontend/web_assets/nomad-karaoke-logo.svg +5 -0
  192. lyrics_transcriber/output/generator.py +17 -3
  193. lyrics_transcriber/output/video.py +60 -95
  194. lyrics_transcriber/frontend/web_assets/assets/index-BECn1o8Q.js.map +0 -1
  195. {karaoke_gen-0.90.1.dist-info → karaoke_gen-0.99.3.dist-info}/WHEEL +0 -0
  196. {karaoke_gen-0.90.1.dist-info → karaoke_gen-0.99.3.dist-info}/entry_points.txt +0 -0
  197. {karaoke_gen-0.90.1.dist-info → karaoke_gen-0.99.3.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,330 @@
1
+ """
2
+ Template service for managing email templates stored in GCS.
3
+
4
+ Templates are stored in GCS bucket and rendered with job-specific variables.
5
+ This allows updating email content without code deployment.
6
+
7
+ Template locations:
8
+ - gs://{bucket}/templates/job-completion.txt - Job completion email (plain text)
9
+ - gs://{bucket}/templates/action-needed-lyrics.txt - Lyrics review reminder
10
+ - gs://{bucket}/templates/action-needed-instrumental.txt - Instrumental selection reminder
11
+ """
12
+ import logging
13
+ import re
14
+ from typing import Optional, Dict, Any
15
+
16
+ from backend.config import get_settings
17
+
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ # Default templates (fallback if GCS fetch fails)
23
+ DEFAULT_JOB_COMPLETION_TEMPLATE = """Hi {name},
24
+
25
+ Thanks for your order!
26
+
27
+ Here's the link for the karaoke video published to YouTube:
28
+ {youtube_url}
29
+
30
+ Here's the dropbox folder with all the finished files and source files, including:
31
+ - "(Final Karaoke Lossless).mkv": combined karaoke video in 4k H264 with lossless FLAC audio
32
+ - "(Final Karaoke).mp4": combined karaoke video with title/end screen in 4k H264/AAC
33
+ - "(Final Karaoke 720p).mp4": combined karaoke video in 720p H264/AAC (smaller file for older systems)
34
+ - "(With Vocals).mp4": sing along video in 4k H264/AAC with original vocals
35
+ - "(Karaoke).mov": karaoke video output from MidiCo (no title/end screen)
36
+ - "(Title).mov"/"(End).mov": title card and end screen videos
37
+ - "(Final Karaoke CDG).zip": CDG+MP3 format for older/commercial karaoke systems
38
+ - "(Final Karaoke TXT).zip": TXT+MP3 format for Power Karaoke
39
+ - stems/*.flac: various separated instrumental and vocal audio stems in lossless format
40
+ - lyrics/*.txt song lyrics from various sources in plain text format
41
+
42
+ {dropbox_url}
43
+
44
+ Let me know if anything isn't perfect and I'll happily tweak / fix, or if you need it in any other format I can probably convert it for you!
45
+
46
+ If you have a moment, I'd really appreciate your feedback (takes 2 minutes):
47
+ {feedback_url}
48
+
49
+ Thanks again and have a great day!
50
+ -Andrew
51
+ """
52
+
53
+ DEFAULT_ACTION_NEEDED_LYRICS_TEMPLATE = """Hi {name},
54
+
55
+ Your karaoke video for "{artist} - {title}" is ready for lyrics review!
56
+
57
+ Our system has transcribed and synchronized the lyrics, but they may need some corrections. Please review and make any corrections necessary so we can finish generating your video.
58
+
59
+ Review your lyrics here:
60
+ {review_url}
61
+
62
+ This usually takes just a few minutes.
63
+
64
+ Thanks!
65
+ -Andrew
66
+ """
67
+
68
+ DEFAULT_ACTION_NEEDED_INSTRUMENTAL_TEMPLATE = """Hi {name},
69
+
70
+ Your karaoke video for "{artist} - {title}" is almost done!
71
+
72
+ We've separated the audio into different versions. Please select which instrumental track you'd like to use for the final video.
73
+
74
+ Select your instrumental here:
75
+ {instrumental_url}
76
+
77
+ Thanks!
78
+ -Andrew
79
+ """
80
+
81
+
82
+ class TemplateService:
83
+ """
84
+ Service for fetching and rendering email templates from GCS.
85
+
86
+ Templates support the following variables:
87
+ - {name} - User's display name or "there" if unknown
88
+ - {youtube_url} - YouTube video URL
89
+ - {dropbox_url} - Dropbox folder URL
90
+ - {artist} - Artist name
91
+ - {title} - Song title
92
+ - {job_id} - Job ID
93
+ - {review_url} - Lyrics review URL
94
+ - {instrumental_url} - Instrumental selection URL
95
+ - {feedback_url} - Feedback form URL
96
+ """
97
+
98
+ TEMPLATE_PREFIX = "templates/"
99
+
100
+ def __init__(self):
101
+ """Initialize template service."""
102
+ self.settings = get_settings()
103
+ self._storage_client = None
104
+ self._bucket = None
105
+
106
+ @property
107
+ def storage_client(self):
108
+ """Lazy-initialize storage client."""
109
+ if self._storage_client is None:
110
+ from google.cloud import storage
111
+ self._storage_client = storage.Client(project=self.settings.google_cloud_project)
112
+ return self._storage_client
113
+
114
+ @property
115
+ def bucket(self):
116
+ """Get the GCS bucket for templates."""
117
+ if self._bucket is None:
118
+ self._bucket = self.storage_client.bucket(self.settings.gcs_bucket_name)
119
+ return self._bucket
120
+
121
+ def _fetch_template_from_gcs(self, template_name: str) -> Optional[str]:
122
+ """
123
+ Fetch a template from GCS.
124
+
125
+ Args:
126
+ template_name: Name of template file (e.g., "job-completion.txt")
127
+
128
+ Returns:
129
+ Template content as string, or None if not found
130
+ """
131
+ blob_path = f"{self.TEMPLATE_PREFIX}{template_name}"
132
+ try:
133
+ blob = self.bucket.blob(blob_path)
134
+ if blob.exists():
135
+ content = blob.download_as_text()
136
+ logger.debug(f"Fetched template from GCS: {blob_path}")
137
+ return content
138
+ else:
139
+ logger.warning(f"Template not found in GCS: {blob_path}")
140
+ return None
141
+ except Exception as e:
142
+ logger.error(f"Failed to fetch template {blob_path}: {e}")
143
+ return None
144
+
145
+ def get_job_completion_template(self) -> str:
146
+ """Get the job completion email template."""
147
+ template = self._fetch_template_from_gcs("job-completion.txt")
148
+ if template is None:
149
+ logger.info("Using default job completion template")
150
+ return DEFAULT_JOB_COMPLETION_TEMPLATE
151
+ return template
152
+
153
+ def get_action_needed_lyrics_template(self) -> str:
154
+ """Get the lyrics review reminder template."""
155
+ template = self._fetch_template_from_gcs("action-needed-lyrics.txt")
156
+ if template is None:
157
+ logger.info("Using default lyrics reminder template")
158
+ return DEFAULT_ACTION_NEEDED_LYRICS_TEMPLATE
159
+ return template
160
+
161
+ def get_action_needed_instrumental_template(self) -> str:
162
+ """Get the instrumental selection reminder template."""
163
+ template = self._fetch_template_from_gcs("action-needed-instrumental.txt")
164
+ if template is None:
165
+ logger.info("Using default instrumental reminder template")
166
+ return DEFAULT_ACTION_NEEDED_INSTRUMENTAL_TEMPLATE
167
+ return template
168
+
169
+ def render_template(self, template: str, variables: Dict[str, Any]) -> str:
170
+ """
171
+ Render a template with the given variables.
172
+
173
+ Missing variables are replaced with empty strings.
174
+ Handles conditional sections like feedback URL.
175
+
176
+ Args:
177
+ template: Template string with {variable} placeholders
178
+ variables: Dictionary of variable values
179
+
180
+ Returns:
181
+ Rendered template string
182
+ """
183
+ result = template
184
+
185
+ # Handle feedback URL section - remove if not provided
186
+ if not variables.get("feedback_url"):
187
+ # Remove feedback section (lines containing feedback_url placeholder and surrounding text)
188
+ result = re.sub(
189
+ r'\n*If you have a moment.*?\{feedback_url\}\n*',
190
+ '\n',
191
+ result,
192
+ flags=re.DOTALL
193
+ )
194
+ variables["feedback_url"] = ""
195
+
196
+ # Replace all variables
197
+ for key, value in variables.items():
198
+ placeholder = "{" + key + "}"
199
+ result = result.replace(placeholder, str(value) if value else "")
200
+
201
+ # Clean up any remaining unreplaced placeholders
202
+ result = re.sub(r'\{[a-z_]+\}', '', result)
203
+
204
+ return result.strip()
205
+
206
+ def render_job_completion(
207
+ self,
208
+ name: Optional[str] = None,
209
+ youtube_url: Optional[str] = None,
210
+ dropbox_url: Optional[str] = None,
211
+ artist: Optional[str] = None,
212
+ title: Optional[str] = None,
213
+ job_id: Optional[str] = None,
214
+ feedback_url: Optional[str] = None,
215
+ ) -> str:
216
+ """
217
+ Render the job completion email template.
218
+
219
+ Args:
220
+ name: User's display name (defaults to "there")
221
+ youtube_url: YouTube video URL
222
+ dropbox_url: Dropbox folder URL
223
+ artist: Artist name
224
+ title: Song title
225
+ job_id: Job ID
226
+ feedback_url: Feedback form URL (optional)
227
+
228
+ Returns:
229
+ Rendered email content
230
+ """
231
+ template = self.get_job_completion_template()
232
+ variables = {
233
+ "name": name or "there",
234
+ "youtube_url": youtube_url or "[YouTube URL not available]",
235
+ "dropbox_url": dropbox_url or "[Dropbox URL not available]",
236
+ "artist": artist or "Unknown Artist",
237
+ "title": title or "Unknown Title",
238
+ "job_id": job_id or "",
239
+ "feedback_url": feedback_url,
240
+ }
241
+ return self.render_template(template, variables)
242
+
243
+ def render_action_needed_lyrics(
244
+ self,
245
+ name: Optional[str] = None,
246
+ artist: Optional[str] = None,
247
+ title: Optional[str] = None,
248
+ review_url: str = "",
249
+ ) -> str:
250
+ """
251
+ Render the lyrics review reminder template.
252
+
253
+ Args:
254
+ name: User's display name
255
+ artist: Artist name
256
+ title: Song title
257
+ review_url: Lyrics review URL
258
+
259
+ Returns:
260
+ Rendered email content
261
+ """
262
+ template = self.get_action_needed_lyrics_template()
263
+ variables = {
264
+ "name": name or "there",
265
+ "artist": artist or "Unknown Artist",
266
+ "title": title or "Unknown Title",
267
+ "review_url": review_url,
268
+ }
269
+ return self.render_template(template, variables)
270
+
271
+ def render_action_needed_instrumental(
272
+ self,
273
+ name: Optional[str] = None,
274
+ artist: Optional[str] = None,
275
+ title: Optional[str] = None,
276
+ instrumental_url: str = "",
277
+ ) -> str:
278
+ """
279
+ Render the instrumental selection reminder template.
280
+
281
+ Args:
282
+ name: User's display name
283
+ artist: Artist name
284
+ title: Song title
285
+ instrumental_url: Instrumental selection URL
286
+
287
+ Returns:
288
+ Rendered email content
289
+ """
290
+ template = self.get_action_needed_instrumental_template()
291
+ variables = {
292
+ "name": name or "there",
293
+ "artist": artist or "Unknown Artist",
294
+ "title": title or "Unknown Title",
295
+ "instrumental_url": instrumental_url,
296
+ }
297
+ return self.render_template(template, variables)
298
+
299
+ def upload_template(self, template_name: str, content: str) -> bool:
300
+ """
301
+ Upload a template to GCS.
302
+
303
+ Args:
304
+ template_name: Name of template file (e.g., "job-completion.txt")
305
+ content: Template content
306
+
307
+ Returns:
308
+ True if upload successful
309
+ """
310
+ blob_path = f"{self.TEMPLATE_PREFIX}{template_name}"
311
+ try:
312
+ blob = self.bucket.blob(blob_path)
313
+ blob.upload_from_string(content, content_type="text/plain")
314
+ logger.info(f"Uploaded template to GCS: {blob_path}")
315
+ return True
316
+ except Exception as e:
317
+ logger.error(f"Failed to upload template {blob_path}: {e}")
318
+ return False
319
+
320
+
321
+ # Global instance
322
+ _template_service: Optional[TemplateService] = None
323
+
324
+
325
+ def get_template_service() -> TemplateService:
326
+ """Get the global template service instance."""
327
+ global _template_service
328
+ if _template_service is None:
329
+ _template_service = TemplateService()
330
+ return _template_service