coursecode 0.1.0

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 (362) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +322 -0
  3. package/THIRD_PARTY_NOTICES.md +22 -0
  4. package/bin/cli.js +331 -0
  5. package/framework/assets/logo-coursecode-black.svg +14 -0
  6. package/framework/assets/logo-coursecode-white.svg +14 -0
  7. package/framework/assets/logo-coursecode.svg +14 -0
  8. package/framework/css/01-base.css +160 -0
  9. package/framework/css/02-layout.css +499 -0
  10. package/framework/css/accessibility.css +834 -0
  11. package/framework/css/components/accordions.css +710 -0
  12. package/framework/css/components/assessments.css +520 -0
  13. package/framework/css/components/audio-player.css +570 -0
  14. package/framework/css/components/badges.css +80 -0
  15. package/framework/css/components/breadcrumbs.css +87 -0
  16. package/framework/css/components/buttons.css +707 -0
  17. package/framework/css/components/callouts.css +1280 -0
  18. package/framework/css/components/cards.css +475 -0
  19. package/framework/css/components/carousel.css +193 -0
  20. package/framework/css/components/checkbox-group.css +123 -0
  21. package/framework/css/components/checklist.css +203 -0
  22. package/framework/css/components/collapse.css +96 -0
  23. package/framework/css/components/comparison.css +33 -0
  24. package/framework/css/components/content-image.css +36 -0
  25. package/framework/css/components/document-gallery.css +425 -0
  26. package/framework/css/components/dropdown.css +115 -0
  27. package/framework/css/components/embed-frame.css +142 -0
  28. package/framework/css/components/engagement.css +412 -0
  29. package/framework/css/components/features.css +35 -0
  30. package/framework/css/components/flip-cards.css +253 -0
  31. package/framework/css/components/footer.css +353 -0
  32. package/framework/css/components/forms.css +294 -0
  33. package/framework/css/components/hero.css +216 -0
  34. package/framework/css/components/images.css +528 -0
  35. package/framework/css/components/interactive-timeline.css +274 -0
  36. package/framework/css/components/intro-cards.css +30 -0
  37. package/framework/css/components/lightbox.css +666 -0
  38. package/framework/css/components/loading.css +65 -0
  39. package/framework/css/components/modals.css +235 -0
  40. package/framework/css/components/notifications.css +107 -0
  41. package/framework/css/components/quote.css +150 -0
  42. package/framework/css/components/sidebar.css +684 -0
  43. package/framework/css/components/slide-header.css +52 -0
  44. package/framework/css/components/spinner.css +62 -0
  45. package/framework/css/components/stats.css +44 -0
  46. package/framework/css/components/steps.css +232 -0
  47. package/framework/css/components/tables.css +90 -0
  48. package/framework/css/components/tabs.css +347 -0
  49. package/framework/css/components/timeline.css +154 -0
  50. package/framework/css/components/toggle.css +95 -0
  51. package/framework/css/components/tooltip.css +226 -0
  52. package/framework/css/components/video-player.css +438 -0
  53. package/framework/css/design-tokens.css +707 -0
  54. package/framework/css/framework.css +86 -0
  55. package/framework/css/interactions/accessibility.css +75 -0
  56. package/framework/css/interactions/base.css +92 -0
  57. package/framework/css/interactions/drag-drop.css +295 -0
  58. package/framework/css/interactions/fill-in-the-blank.css +236 -0
  59. package/framework/css/interactions/hotspots.css +69 -0
  60. package/framework/css/interactions/index.css +45 -0
  61. package/framework/css/interactions/interactive-image.css +359 -0
  62. package/framework/css/interactions/likert.css +126 -0
  63. package/framework/css/interactions/matching.css +354 -0
  64. package/framework/css/interactions/numeric-input.css +78 -0
  65. package/framework/css/interactions/sequencing.css +378 -0
  66. package/framework/css/interactions/true-false.css +177 -0
  67. package/framework/css/layouts/article.css +258 -0
  68. package/framework/css/layouts/base.css +30 -0
  69. package/framework/css/layouts/canvas.css +38 -0
  70. package/framework/css/layouts/focused.css +236 -0
  71. package/framework/css/layouts/index.css +29 -0
  72. package/framework/css/layouts/presentation.css +191 -0
  73. package/framework/css/layouts/traditional.css +52 -0
  74. package/framework/css/responsive.css +439 -0
  75. package/framework/css/utilities/accessibility-utils.css +59 -0
  76. package/framework/css/utilities/animations.css +419 -0
  77. package/framework/css/utilities/borders.css +72 -0
  78. package/framework/css/utilities/colors.css +76 -0
  79. package/framework/css/utilities/container.css +46 -0
  80. package/framework/css/utilities/decorative.css +442 -0
  81. package/framework/css/utilities/display.css +257 -0
  82. package/framework/css/utilities/flexbox.css +80 -0
  83. package/framework/css/utilities/grid.css +69 -0
  84. package/framework/css/utilities/icons.css +534 -0
  85. package/framework/css/utilities/lists.css +190 -0
  86. package/framework/css/utilities/spacing.css +167 -0
  87. package/framework/css/utilities/tables.css +81 -0
  88. package/framework/css/utilities/typography.css +159 -0
  89. package/framework/css/utilities/visibility.css +117 -0
  90. package/framework/docs/COURSE_AUTHORING_GUIDE.md +1773 -0
  91. package/framework/docs/COURSE_OUTLINE_GUIDE.md +725 -0
  92. package/framework/docs/COURSE_OUTLINE_TEMPLATE.md +161 -0
  93. package/framework/docs/DATA_MODEL.md +409 -0
  94. package/framework/docs/FRAMEWORK_GUIDE.md +1088 -0
  95. package/framework/docs/USER_GUIDE.md +583 -0
  96. package/framework/docs/examples/cloudflare-channel-relay.js +169 -0
  97. package/framework/docs/examples/cloudflare-data-worker.js +102 -0
  98. package/framework/docs/examples/cloudflare-error-worker.js +228 -0
  99. package/framework/index.html +175 -0
  100. package/framework/js/app/AppActions.js +410 -0
  101. package/framework/js/app/AppState.js +225 -0
  102. package/framework/js/app/AppUI.js +616 -0
  103. package/framework/js/assessment/AssessmentActions.js +615 -0
  104. package/framework/js/assessment/AssessmentFactory.js +471 -0
  105. package/framework/js/assessment/AssessmentState.js +322 -0
  106. package/framework/js/assessment/AssessmentUI.js +451 -0
  107. package/framework/js/automation/api-engagement.js +196 -0
  108. package/framework/js/automation/api-interactions.js +167 -0
  109. package/framework/js/automation/api.js +242 -0
  110. package/framework/js/automation/index.js +41 -0
  111. package/framework/js/components/interactions/drag-drop.js +884 -0
  112. package/framework/js/components/interactions/fill-in.js +535 -0
  113. package/framework/js/components/interactions/hotspot.js +702 -0
  114. package/framework/js/components/interactions/interaction-base.js +511 -0
  115. package/framework/js/components/interactions/likert.js +301 -0
  116. package/framework/js/components/interactions/matching.js +699 -0
  117. package/framework/js/components/interactions/multiple-choice.js +377 -0
  118. package/framework/js/components/interactions/numeric.js +271 -0
  119. package/framework/js/components/interactions/sequencing.js +423 -0
  120. package/framework/js/components/interactions/true-false.js +241 -0
  121. package/framework/js/components/ui-components/accordion.js +442 -0
  122. package/framework/js/components/ui-components/alert.js +88 -0
  123. package/framework/js/components/ui-components/audio-player.js +1193 -0
  124. package/framework/js/components/ui-components/callout.js +121 -0
  125. package/framework/js/components/ui-components/carousel.js +145 -0
  126. package/framework/js/components/ui-components/checkbox-group.js +87 -0
  127. package/framework/js/components/ui-components/checklist.js +40 -0
  128. package/framework/js/components/ui-components/collapse.js +114 -0
  129. package/framework/js/components/ui-components/comparison.js +30 -0
  130. package/framework/js/components/ui-components/conditional-display.js +150 -0
  131. package/framework/js/components/ui-components/content-image.js +41 -0
  132. package/framework/js/components/ui-components/dropdown.js +262 -0
  133. package/framework/js/components/ui-components/embed-frame.js +274 -0
  134. package/framework/js/components/ui-components/features.js +33 -0
  135. package/framework/js/components/ui-components/flip-card.js +230 -0
  136. package/framework/js/components/ui-components/form-validator.js +76 -0
  137. package/framework/js/components/ui-components/hero.js +49 -0
  138. package/framework/js/components/ui-components/index.js +12 -0
  139. package/framework/js/components/ui-components/interactive-image.js +235 -0
  140. package/framework/js/components/ui-components/interactive-timeline.js +285 -0
  141. package/framework/js/components/ui-components/intro-cards.js +35 -0
  142. package/framework/js/components/ui-components/lightbox.js +652 -0
  143. package/framework/js/components/ui-components/modal.js +386 -0
  144. package/framework/js/components/ui-components/notifications.js +145 -0
  145. package/framework/js/components/ui-components/progress.js +88 -0
  146. package/framework/js/components/ui-components/quote.js +41 -0
  147. package/framework/js/components/ui-components/stats.js +33 -0
  148. package/framework/js/components/ui-components/steps.js +41 -0
  149. package/framework/js/components/ui-components/tabs.js +255 -0
  150. package/framework/js/components/ui-components/timeline.js +42 -0
  151. package/framework/js/components/ui-components/toggle-group.js +73 -0
  152. package/framework/js/components/ui-components/tooltip.js +458 -0
  153. package/framework/js/components/ui-components/value-display.js +133 -0
  154. package/framework/js/components/ui-components/video-player.js +686 -0
  155. package/framework/js/core/component-catalog.js +121 -0
  156. package/framework/js/core/event-bus.js +178 -0
  157. package/framework/js/core/interaction-catalog.js +149 -0
  158. package/framework/js/dev/runtime-linter.js +1725 -0
  159. package/framework/js/drivers/cmi5-driver.js +768 -0
  160. package/framework/js/drivers/driver-factory.js +77 -0
  161. package/framework/js/drivers/driver-interface.js +110 -0
  162. package/framework/js/drivers/http-driver-base.js +241 -0
  163. package/framework/js/drivers/lti-driver.js +508 -0
  164. package/framework/js/drivers/proxy-driver.js +444 -0
  165. package/framework/js/drivers/scorm-12-driver.js +560 -0
  166. package/framework/js/drivers/scorm-2004-driver.js +775 -0
  167. package/framework/js/drivers/scorm-driver-base.js +112 -0
  168. package/framework/js/engagement/engagement-manager.js +404 -0
  169. package/framework/js/engagement/engagement-progress.js +191 -0
  170. package/framework/js/engagement/engagement-trackers.js +215 -0
  171. package/framework/js/engagement/requirement-strategies.js +268 -0
  172. package/framework/js/main.js +727 -0
  173. package/framework/js/managers/accessibility-manager.js +499 -0
  174. package/framework/js/managers/assessment-manager.js +230 -0
  175. package/framework/js/managers/audio-manager.js +944 -0
  176. package/framework/js/managers/comment-manager.js +88 -0
  177. package/framework/js/managers/flag-manager.js +86 -0
  178. package/framework/js/managers/interaction-manager.js +254 -0
  179. package/framework/js/managers/interaction-registry.js +96 -0
  180. package/framework/js/managers/objective-manager.js +423 -0
  181. package/framework/js/managers/score-manager.js +441 -0
  182. package/framework/js/managers/video-manager.js +536 -0
  183. package/framework/js/navigation/Breadcrumbs.js +234 -0
  184. package/framework/js/navigation/NavigationActions.js +1132 -0
  185. package/framework/js/navigation/NavigationState.js +276 -0
  186. package/framework/js/navigation/NavigationUI.js +574 -0
  187. package/framework/js/navigation/document-gallery.js +357 -0
  188. package/framework/js/navigation/navigation-helpers.js +175 -0
  189. package/framework/js/navigation/navigation-validators.js +174 -0
  190. package/framework/js/state/index.js +8 -0
  191. package/framework/js/state/lms-connection.js +482 -0
  192. package/framework/js/state/lms-error-utils.js +58 -0
  193. package/framework/js/state/state-commits.js +200 -0
  194. package/framework/js/state/state-domains.js +86 -0
  195. package/framework/js/state/state-manager.js +502 -0
  196. package/framework/js/state/state-validation.js +311 -0
  197. package/framework/js/state/transaction-log.js +41 -0
  198. package/framework/js/state/xapi-statement-service.js +325 -0
  199. package/framework/js/utilities/access-control.js +99 -0
  200. package/framework/js/utilities/breakpoint-manager.js +315 -0
  201. package/framework/js/utilities/canvas-slide.js +35 -0
  202. package/framework/js/utilities/conditional-display.js +388 -0
  203. package/framework/js/utilities/course-channel.js +214 -0
  204. package/framework/js/utilities/course-helpers.js +420 -0
  205. package/framework/js/utilities/data-reporter.js +273 -0
  206. package/framework/js/utilities/error-reporter.js +313 -0
  207. package/framework/js/utilities/hotspot-helper.js +341 -0
  208. package/framework/js/utilities/icons.js +348 -0
  209. package/framework/js/utilities/logger.js +92 -0
  210. package/framework/js/utilities/markdown-renderer.js +45 -0
  211. package/framework/js/utilities/scroll-tracker.js +68 -0
  212. package/framework/js/utilities/ui-initializer.js +146 -0
  213. package/framework/js/utilities/utilities.js +293 -0
  214. package/framework/js/utilities/view-manager.js +227 -0
  215. package/framework/js/validation/html-validators.js +422 -0
  216. package/framework/js/validation/scorm-validators.js +438 -0
  217. package/framework/js/vendor/pipwerks.js +931 -0
  218. package/framework/scripts/generate-narration.js +629 -0
  219. package/framework/scripts/tts-providers/azure-provider.js +178 -0
  220. package/framework/scripts/tts-providers/base-provider.js +81 -0
  221. package/framework/scripts/tts-providers/deepgram-provider.js +135 -0
  222. package/framework/scripts/tts-providers/elevenlabs-provider.js +148 -0
  223. package/framework/scripts/tts-providers/google-provider.js +272 -0
  224. package/framework/scripts/tts-providers/index.js +158 -0
  225. package/framework/scripts/tts-providers/openai-provider.js +143 -0
  226. package/framework/version.json +63 -0
  227. package/lib/authoring-api.js +919 -0
  228. package/lib/build-linter.js +450 -0
  229. package/lib/build-packaging.js +186 -0
  230. package/lib/build.js +88 -0
  231. package/lib/cloud.js +691 -0
  232. package/lib/convert.js +341 -0
  233. package/lib/course-parser.js +936 -0
  234. package/lib/course-writer.js +258 -0
  235. package/lib/create.js +248 -0
  236. package/lib/css-index.js +237 -0
  237. package/lib/dev.js +51 -0
  238. package/lib/export-content.js +1246 -0
  239. package/lib/headless-browser.js +413 -0
  240. package/lib/import.js +377 -0
  241. package/lib/index.js +80 -0
  242. package/lib/info.js +79 -0
  243. package/lib/interaction-formatters.js +568 -0
  244. package/lib/manifest/cmi5-manifest.js +63 -0
  245. package/lib/manifest/lti-tool-config.js +53 -0
  246. package/lib/manifest/manifest-factory.js +99 -0
  247. package/lib/manifest/scorm-12-manifest.js +61 -0
  248. package/lib/manifest/scorm-2004-manifest.js +94 -0
  249. package/lib/manifest/scorm-proxy-manifest.js +104 -0
  250. package/lib/manifest-parser.js +96 -0
  251. package/lib/mcp-prompts.js +753 -0
  252. package/lib/mcp-server.js +316 -0
  253. package/lib/narration.js +53 -0
  254. package/lib/pdf-structure.js +142 -0
  255. package/lib/preview-export.js +231 -0
  256. package/lib/preview-routes-api.js +662 -0
  257. package/lib/preview-routes-editing.js +159 -0
  258. package/lib/preview-routes-lms.js +230 -0
  259. package/lib/preview-server.js +564 -0
  260. package/lib/project-utils.js +269 -0
  261. package/lib/proxy-templates/proxy.html +68 -0
  262. package/lib/proxy-templates/scorm-bridge.js +112 -0
  263. package/lib/scaffold.js +193 -0
  264. package/lib/schema-extractor.js +361 -0
  265. package/lib/slide-source-editor.js +586 -0
  266. package/lib/stub-player/app-viewer.js +195 -0
  267. package/lib/stub-player/app.js +370 -0
  268. package/lib/stub-player/catalog-panel.js +312 -0
  269. package/lib/stub-player/config-panel.js +1303 -0
  270. package/lib/stub-player/content-generator.js +586 -0
  271. package/lib/stub-player/content-viewer.js +173 -0
  272. package/lib/stub-player/debug-panel.js +420 -0
  273. package/lib/stub-player/edit-mode.js +922 -0
  274. package/lib/stub-player/edit-utils.js +400 -0
  275. package/lib/stub-player/header-bar.js +354 -0
  276. package/lib/stub-player/interaction-editor.js +210 -0
  277. package/lib/stub-player/interactions-panel.js +565 -0
  278. package/lib/stub-player/lms-api.js +1094 -0
  279. package/lib/stub-player/login-screen.js +74 -0
  280. package/lib/stub-player/outline-mode.js +689 -0
  281. package/lib/stub-player/styles/_assessments-panel.css +245 -0
  282. package/lib/stub-player/styles/_base.css +89 -0
  283. package/lib/stub-player/styles/_catalog-icons.css +96 -0
  284. package/lib/stub-player/styles/_catalog-panel.css +291 -0
  285. package/lib/stub-player/styles/_config-panel.css +636 -0
  286. package/lib/stub-player/styles/_content-viewer.css +834 -0
  287. package/lib/stub-player/styles/_debug-panel.css +576 -0
  288. package/lib/stub-player/styles/_edit-mode.css +128 -0
  289. package/lib/stub-player/styles/_header-bar.css +343 -0
  290. package/lib/stub-player/styles/_interaction-editor.css +140 -0
  291. package/lib/stub-player/styles/_interactions-panel.css +1038 -0
  292. package/lib/stub-player/styles/_login-screen.css +102 -0
  293. package/lib/stub-player/styles/_outline-mode.css +752 -0
  294. package/lib/stub-player/styles.css +15 -0
  295. package/lib/stub-player.js +160 -0
  296. package/lib/test-data-reporting.js +176 -0
  297. package/lib/test-error-reporting.js +146 -0
  298. package/lib/token.js +86 -0
  299. package/lib/upgrade.js +257 -0
  300. package/lib/validation-rules.js +517 -0
  301. package/lib/vite-plugin-content-discovery.js +296 -0
  302. package/package.json +108 -0
  303. package/schemas/XMLSchema.dtd +402 -0
  304. package/schemas/adlcp_v1p3.xsd +111 -0
  305. package/schemas/adlnav_v1p3.xsd +61 -0
  306. package/schemas/adlseq_v1p3.xsd +93 -0
  307. package/schemas/common/anyElement.xsd +27 -0
  308. package/schemas/common/dataTypes.xsd +138 -0
  309. package/schemas/common/elementNames.xsd +767 -0
  310. package/schemas/common/elementTypes.xsd +786 -0
  311. package/schemas/common/rootElement.xsd +31 -0
  312. package/schemas/common/vocabTypes.xsd +345 -0
  313. package/schemas/common/vocabValues.xsd +257 -0
  314. package/schemas/datatypes.dtd +203 -0
  315. package/schemas/ims_xml.xsd +35 -0
  316. package/schemas/imscp_v1p1.xsd +368 -0
  317. package/schemas/imsss_v1p0.xsd +67 -0
  318. package/schemas/imsss_v1p0auxresource.xsd +19 -0
  319. package/schemas/imsss_v1p0control.xsd +20 -0
  320. package/schemas/imsss_v1p0delivery.xsd +17 -0
  321. package/schemas/imsss_v1p0limit.xsd +47 -0
  322. package/schemas/imsss_v1p0objective.xsd +67 -0
  323. package/schemas/imsss_v1p0random.xsd +16 -0
  324. package/schemas/imsss_v1p0rollup.xsd +46 -0
  325. package/schemas/imsss_v1p0seqrule.xsd +108 -0
  326. package/schemas/imsss_v1p0util.xsd +94 -0
  327. package/schemas/license.txt +17 -0
  328. package/schemas/lom.xsd +102 -0
  329. package/schemas/lomCustom.xsd +62 -0
  330. package/schemas/lomLoose.xsd +62 -0
  331. package/schemas/lomStrict.xsd +62 -0
  332. package/schemas/xml.xsd +81 -0
  333. package/template/.env.example +92 -0
  334. package/template/course/assets/audio/example-intro.mp3 +0 -0
  335. package/template/course/assets/audio/example-ui-demo--compact-player.mp3 +0 -0
  336. package/template/course/assets/audio/example-ui-demo--demo-modal.mp3 +0 -0
  337. package/template/course/assets/audio/example-ui-demo--full-player.mp3 +0 -0
  338. package/template/course/assets/docs/example_md_1.md +39 -0
  339. package/template/course/assets/docs/example_md_2.md +41 -0
  340. package/template/course/assets/docs/example_pdf_1_thumbnail.png +0 -0
  341. package/template/course/assets/docs/example_pdf_2.pdf +0 -0
  342. package/template/course/assets/images/course-architecture.svg +36 -0
  343. package/template/course/assets/images/logo.svg +14 -0
  344. package/template/course/assets/widgets/counter-demo.html +190 -0
  345. package/template/course/assets/widgets/gravity-painter.html +384 -0
  346. package/template/course/course-config.js +539 -0
  347. package/template/course/icons.js +19 -0
  348. package/template/course/interactions/PLUGIN_GUIDE.md +97 -0
  349. package/template/course/slides/example-course-structure.js +138 -0
  350. package/template/course/slides/example-final-exam.js +144 -0
  351. package/template/course/slides/example-finishing.js +127 -0
  352. package/template/course/slides/example-interactions-showcase.js +615 -0
  353. package/template/course/slides/example-preview-tour.js +129 -0
  354. package/template/course/slides/example-remedial.js +143 -0
  355. package/template/course/slides/example-summary.js +103 -0
  356. package/template/course/slides/example-ui-showcase.js +1805 -0
  357. package/template/course/slides/example-welcome.js +123 -0
  358. package/template/course/slides/example-workflow.js +140 -0
  359. package/template/course/theme.css +165 -0
  360. package/template/eslint.config.js +47 -0
  361. package/template/package.json +28 -0
  362. package/template/vite.config.js +339 -0
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Azure Cognitive Services TTS Provider
3
+ *
4
+ * Microsoft's neural TTS with extensive language and voice support.
5
+ *
6
+ * Environment variables:
7
+ * AZURE_SPEECH_KEY - Required: Your Azure Speech service key
8
+ * AZURE_SPEECH_REGION - Required: Azure region (e.g., eastus, westeurope)
9
+ * AZURE_VOICE - Optional: Default voice name (e.g., en-US-JennyNeural)
10
+ *
11
+ * Voice settings (per-narration):
12
+ * voice - Full voice name (e.g., en-US-JennyNeural)
13
+ * style - Speaking style (e.g., cheerful, sad, angry) - neural voices only
14
+ * rate - Speaking rate (e.g., "+10%", "-20%", "1.2")
15
+ * pitch - Pitch adjustment (e.g., "+5%", "-10%")
16
+ */
17
+
18
+ import { BaseTTSProvider } from './base-provider.js';
19
+
20
+ // Default settings
21
+ const DEFAULT_VOICE = 'en-US-JennyNeural';
22
+ const DEFAULT_OUTPUT_FORMAT = 'audio-24khz-48kbitrate-mono-mp3';
23
+
24
+ export class AzureProvider extends BaseTTSProvider {
25
+ constructor(config = {}) {
26
+ super(config);
27
+ this.name = 'azure';
28
+ this.apiKey = config.apiKey || process.env.AZURE_SPEECH_KEY;
29
+ this.region = config.region || process.env.AZURE_SPEECH_REGION;
30
+ }
31
+
32
+ static getRequiredEnvVars() {
33
+ return [
34
+ { name: 'AZURE_SPEECH_KEY', required: true, description: 'Azure Speech service key' },
35
+ { name: 'AZURE_SPEECH_REGION', required: true, description: 'Azure region (e.g., eastus)' },
36
+ { name: 'AZURE_VOICE', required: false, description: 'Default voice (e.g., en-US-JennyNeural)' }
37
+ ];
38
+ }
39
+
40
+ validateConfig() {
41
+ if (!this.apiKey) {
42
+ throw new Error('AZURE_SPEECH_KEY not set in environment or .env file');
43
+ }
44
+ if (!this.region) {
45
+ throw new Error('AZURE_SPEECH_REGION not set in environment or .env file');
46
+ }
47
+ }
48
+
49
+ getDefaultVoiceId() {
50
+ return process.env.AZURE_VOICE || DEFAULT_VOICE;
51
+ }
52
+
53
+ getDefaultSettings() {
54
+ return {
55
+ voice: this.getDefaultVoiceId(),
56
+ style: null,
57
+ rate: null,
58
+ pitch: null
59
+ };
60
+ }
61
+
62
+ normalizeSettings(settings) {
63
+ const normalized = { ...settings };
64
+
65
+ // Map 'voice_id' to 'voice'
66
+ if (settings.voice_id && !settings.voice) {
67
+ normalized.voice = settings.voice_id;
68
+ delete normalized.voice_id;
69
+ }
70
+
71
+ // Map 'speed' to 'rate'
72
+ if (settings.speed && !settings.rate) {
73
+ // Convert numeric speed to percentage
74
+ const speed = parseFloat(settings.speed);
75
+ if (!isNaN(speed)) {
76
+ const percent = Math.round((speed - 1) * 100);
77
+ normalized.rate = percent >= 0 ? `+${percent}%` : `${percent}%`;
78
+ }
79
+ delete normalized.speed;
80
+ }
81
+
82
+ return normalized;
83
+ }
84
+
85
+ async getVoices() {
86
+ this.validateConfig();
87
+
88
+ const url = `https://${this.region}.tts.speech.microsoft.com/cognitiveservices/voices/list`;
89
+
90
+ const response = await fetch(url, {
91
+ headers: {
92
+ 'Ocp-Apim-Subscription-Key': this.apiKey
93
+ }
94
+ });
95
+
96
+ if (!response.ok) {
97
+ throw new Error(`Failed to fetch voices: ${response.status}`);
98
+ }
99
+
100
+ const voices = await response.json();
101
+ return voices.map(v => ({
102
+ id: v.ShortName,
103
+ name: v.DisplayName,
104
+ description: `${v.LocaleName} - ${v.VoiceType}`
105
+ }));
106
+ }
107
+
108
+ /**
109
+ * Build SSML for the request
110
+ */
111
+ _buildSSML(text, settings) {
112
+ const voice = settings.voice || this.getDefaultVoiceId();
113
+
114
+ // Build prosody attributes
115
+ const prosodyAttrs = [];
116
+ if (settings.rate) prosodyAttrs.push(`rate="${settings.rate}"`);
117
+ if (settings.pitch) prosodyAttrs.push(`pitch="${settings.pitch}"`);
118
+
119
+ // Build express-as for style (neural voices only)
120
+ let content = this._escapeXml(text);
121
+
122
+ if (settings.style) {
123
+ content = `<mstts:express-as style="${settings.style}">${content}</mstts:express-as>`;
124
+ }
125
+
126
+ if (prosodyAttrs.length > 0) {
127
+ content = `<prosody ${prosodyAttrs.join(' ')}>${content}</prosody>`;
128
+ }
129
+
130
+ return `<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xmlns:mstts="http://www.w3.org/2001/mstts" xml:lang="en-US">
131
+ <voice name="${voice}">
132
+ ${content}
133
+ </voice>
134
+ </speak>`;
135
+ }
136
+
137
+ _escapeXml(text) {
138
+ return text
139
+ .replace(/&/g, '&amp;')
140
+ .replace(/</g, '&lt;')
141
+ .replace(/>/g, '&gt;')
142
+ .replace(/"/g, '&quot;')
143
+ .replace(/'/g, '&apos;');
144
+ }
145
+
146
+ async generateAudio(text, settings = {}) {
147
+ this.validateConfig();
148
+
149
+ const normalizedSettings = this.normalizeSettings(settings);
150
+ const ssml = this._buildSSML(text, normalizedSettings);
151
+
152
+ const url = `https://${this.region}.tts.speech.microsoft.com/cognitiveservices/v1`;
153
+
154
+ let response;
155
+ try {
156
+ response = await fetch(url, {
157
+ method: 'POST',
158
+ headers: {
159
+ 'Ocp-Apim-Subscription-Key': this.apiKey,
160
+ 'Content-Type': 'application/ssml+xml',
161
+ 'X-Microsoft-OutputFormat': DEFAULT_OUTPUT_FORMAT,
162
+ 'User-Agent': 'SCORMNarrationGenerator'
163
+ },
164
+ body: ssml
165
+ });
166
+ } catch (fetchError) {
167
+ const cause = fetchError.cause ? `: ${fetchError.cause.message || fetchError.cause.code}` : '';
168
+ throw new Error(`Network error connecting to Azure Speech API${cause}. Check your internet connection.`);
169
+ }
170
+
171
+ if (!response.ok) {
172
+ const errorText = await response.text();
173
+ throw new Error(`Azure Speech API error (${response.status}): ${errorText}`);
174
+ }
175
+
176
+ return Buffer.from(await response.arrayBuffer());
177
+ }
178
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Base TTS Provider Interface
3
+ *
4
+ * All TTS providers must extend this class and implement the required methods.
5
+ * This enables easy swapping between providers (ElevenLabs, OpenAI, Azure, etc.)
6
+ */
7
+
8
+ export class BaseTTSProvider {
9
+ constructor(config = {}) {
10
+ this.config = config;
11
+ this.name = 'base';
12
+ }
13
+
14
+ /**
15
+ * Get the provider name for logging/identification
16
+ * @returns {string}
17
+ */
18
+ getName() {
19
+ return this.name;
20
+ }
21
+
22
+ /**
23
+ * Validate that the provider is properly configured (API keys, etc.)
24
+ * @throws {Error} If configuration is invalid
25
+ */
26
+ validateConfig() {
27
+ throw new Error('validateConfig() must be implemented by provider');
28
+ }
29
+
30
+ /**
31
+ * Get the list of environment variables this provider requires
32
+ * @returns {Array<{name: string, required: boolean, description: string}>}
33
+ */
34
+ static getRequiredEnvVars() {
35
+ return [];
36
+ }
37
+
38
+ /**
39
+ * Get available voices for this provider
40
+ * @returns {Promise<Array<{id: string, name: string, description?: string}>>}
41
+ */
42
+ async getVoices() {
43
+ throw new Error('getVoices() must be implemented by provider');
44
+ }
45
+
46
+ /**
47
+ * Generate audio from text
48
+ * @param {string} text - The text to convert to speech
49
+ * @param {Object} options - Provider-specific options (voice, model, etc.)
50
+ * @returns {Promise<Buffer>} - Audio buffer (MP3 format preferred)
51
+ */
52
+ async generateAudio(text, _options = {}) {
53
+ throw new Error('generateAudio() must be implemented by provider');
54
+ }
55
+
56
+ /**
57
+ * Get the default voice ID for this provider
58
+ * @returns {string}
59
+ */
60
+ getDefaultVoiceId() {
61
+ throw new Error('getDefaultVoiceId() must be implemented by provider');
62
+ }
63
+
64
+ /**
65
+ * Get default settings for this provider
66
+ * @returns {Object}
67
+ */
68
+ getDefaultSettings() {
69
+ return {};
70
+ }
71
+
72
+ /**
73
+ * Normalize provider-specific settings to a common format
74
+ * This allows course authors to use consistent setting names across providers
75
+ * @param {Object} settings - Settings object (may have provider-specific keys)
76
+ * @returns {Object} - Normalized settings for this provider
77
+ */
78
+ normalizeSettings(settings) {
79
+ return settings;
80
+ }
81
+ }
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Deepgram TTS Provider (Aura)
3
+ *
4
+ * Deepgram's Aura text-to-speech with fast, natural-sounding voices.
5
+ *
6
+ * Environment variables:
7
+ * DEEPGRAM_API_KEY - Required: Your Deepgram API key
8
+ * DEEPGRAM_VOICE - Optional: Default voice (default: aura-2-orion-en)
9
+ *
10
+ * Voice settings (per-narration):
11
+ * voice - Voice name (e.g., aura-2-orion-en, aura-2-luna-en)
12
+ *
13
+ * Available Aura 2 voices:
14
+ * aura-2-asteria-en - Female, American, warm and conversational
15
+ * aura-2-luna-en - Female, American, soft and calm
16
+ * aura-2-stella-en - Female, American, confident and clear
17
+ * aura-2-athena-en - Female, British, refined and articulate
18
+ * aura-2-hera-en - Female, American, authoritative
19
+ * aura-2-orion-en - Male, American, deep and steady
20
+ * aura-2-arcas-en - Male, American, energetic and upbeat
21
+ * aura-2-perseus-en - Male, American, warm and friendly
22
+ * aura-2-angus-en - Male, Irish, warm and friendly
23
+ * aura-2-orpheus-en - Male, American, smooth and rich
24
+ * aura-2-helios-en - Male, British, refined
25
+ * aura-2-zeus-en - Male, American, authoritative
26
+ */
27
+
28
+ import { BaseTTSProvider } from './base-provider.js';
29
+
30
+ // Default settings
31
+ const DEFAULT_VOICE = 'aura-2-hermes-en';
32
+
33
+ // Available Aura 2 voices
34
+ const AURA_VOICES = [
35
+ { id: 'aura-2-asteria-en', name: 'Asteria', description: 'Female, American - warm and conversational' },
36
+ { id: 'aura-2-luna-en', name: 'Luna', description: 'Female, American - soft and calm' },
37
+ { id: 'aura-2-stella-en', name: 'Stella', description: 'Female, American - confident and clear' },
38
+ { id: 'aura-2-athena-en', name: 'Athena', description: 'Female, British - refined and articulate' },
39
+ { id: 'aura-2-hera-en', name: 'Hera', description: 'Female, American - authoritative' },
40
+ { id: 'aura-2-orion-en', name: 'Orion', description: 'Male, American - deep and steady' },
41
+ { id: 'aura-2-arcas-en', name: 'Arcas', description: 'Male, American - energetic and upbeat' },
42
+ { id: 'aura-2-perseus-en', name: 'Perseus', description: 'Male, American - warm and friendly' },
43
+ { id: 'aura-2-angus-en', name: 'Angus', description: 'Male, Irish - warm and friendly' },
44
+ { id: 'aura-2-orpheus-en', name: 'Orpheus', description: 'Male, American - smooth and rich' },
45
+ { id: 'aura-2-helios-en', name: 'Helios', description: 'Male, British - refined' },
46
+ { id: 'aura-2-zeus-en', name: 'Zeus', description: 'Male, American - authoritative' }
47
+ ];
48
+
49
+ export class DeepgramProvider extends BaseTTSProvider {
50
+ constructor(config = {}) {
51
+ super(config);
52
+ this.name = 'deepgram';
53
+ this.apiKey = config.apiKey || process.env.DEEPGRAM_API_KEY;
54
+ }
55
+
56
+ static getRequiredEnvVars() {
57
+ return [
58
+ { name: 'DEEPGRAM_API_KEY', required: true, description: 'Deepgram API key' },
59
+ { name: 'DEEPGRAM_VOICE', required: false, description: 'Default voice (e.g., aura-asteria-en)' }
60
+ ];
61
+ }
62
+
63
+ validateConfig() {
64
+ if (!this.apiKey) {
65
+ throw new Error('DEEPGRAM_API_KEY not set in environment or .env file');
66
+ }
67
+
68
+ const trimmedKey = this.apiKey.trim();
69
+ if (trimmedKey !== this.apiKey || !trimmedKey) {
70
+ throw new Error('DEEPGRAM_API_KEY contains invalid whitespace - check your .env file');
71
+ }
72
+ }
73
+
74
+ getDefaultVoiceId() {
75
+ return process.env.DEEPGRAM_VOICE || DEFAULT_VOICE;
76
+ }
77
+
78
+ getDefaultSettings() {
79
+ return {
80
+ voice: this.getDefaultVoiceId()
81
+ };
82
+ }
83
+
84
+ normalizeSettings(settings) {
85
+ const normalized = { ...settings };
86
+
87
+ // Map 'voice_id' to 'voice'
88
+ if (settings.voice_id && !settings.voice) {
89
+ normalized.voice = settings.voice_id;
90
+ delete normalized.voice_id;
91
+ }
92
+
93
+ return normalized;
94
+ }
95
+
96
+ async getVoices() {
97
+ // Deepgram voices are fixed, no API call needed
98
+ return AURA_VOICES;
99
+ }
100
+
101
+ async generateAudio(text, settings = {}) {
102
+ this.validateConfig();
103
+
104
+ const normalizedSettings = this.normalizeSettings(settings);
105
+ const defaults = this.getDefaultSettings();
106
+
107
+ const voice = normalizedSettings.voice || defaults.voice;
108
+
109
+ // Deepgram TTS endpoint
110
+ const url = `https://api.deepgram.com/v1/speak?model=${voice}`;
111
+
112
+ let response;
113
+ try {
114
+ response = await fetch(url, {
115
+ method: 'POST',
116
+ headers: {
117
+ 'Authorization': `Token ${this.apiKey.trim()}`,
118
+ 'Content-Type': 'text/plain'
119
+ },
120
+ body: text
121
+ });
122
+ } catch (fetchError) {
123
+ const cause = fetchError.cause ? `: ${fetchError.cause.message || fetchError.cause.code}` : '';
124
+ throw new Error(`Network error connecting to Deepgram API${cause}. Check your internet connection.`);
125
+ }
126
+
127
+ if (!response.ok) {
128
+ const errorText = await response.text();
129
+ throw new Error(`Deepgram API error (${response.status}): ${errorText}`);
130
+ }
131
+
132
+ // Deepgram returns audio directly (not base64)
133
+ return Buffer.from(await response.arrayBuffer());
134
+ }
135
+ }
@@ -0,0 +1,148 @@
1
+ /**
2
+ * ElevenLabs TTS Provider
3
+ *
4
+ * High-quality AI voice synthesis with customizable voice settings.
5
+ *
6
+ * Environment variables:
7
+ * ELEVENLABS_API_KEY - Required: Your ElevenLabs API key
8
+ * ELEVENLABS_VOICE_ID - Optional: Default voice ID (default: Rachel)
9
+ * ELEVENLABS_MODEL_ID - Optional: Model to use (default: eleven_v3)
10
+ *
11
+ * Voice settings (per-narration):
12
+ * voice_id - Voice to use
13
+ * model_id - Model to use
14
+ * stability - Voice stability (0-1, default: 0.5)
15
+ * similarity_boost - Voice similarity (0-1, default: 0.75)
16
+ */
17
+
18
+ import { BaseTTSProvider } from './base-provider.js';
19
+
20
+ // Default voice settings
21
+ const DEFAULT_VOICE_ID = 'S9UjcNYIwfBOtZiDnIQT';
22
+ const DEFAULT_MODEL_ID = 'eleven_v3';
23
+ const DEFAULT_STABILITY = 0.5;
24
+ const DEFAULT_SIMILARITY_BOOST = 0.75;
25
+
26
+ export class ElevenLabsProvider extends BaseTTSProvider {
27
+ constructor(config = {}) {
28
+ super(config);
29
+ this.name = 'elevenlabs';
30
+ this.apiKey = config.apiKey || process.env.ELEVENLABS_API_KEY;
31
+ }
32
+
33
+ static getRequiredEnvVars() {
34
+ return [
35
+ { name: 'ELEVENLABS_API_KEY', required: true, description: 'ElevenLabs API key' },
36
+ { name: 'ELEVENLABS_VOICE_ID', required: false, description: 'Default voice ID (default: Rachel)' },
37
+ { name: 'ELEVENLABS_MODEL_ID', required: false, description: 'Model ID (default: eleven_v3)' }
38
+ ];
39
+ }
40
+
41
+ validateConfig() {
42
+ if (!this.apiKey) {
43
+ throw new Error('ELEVENLABS_API_KEY not set in environment or .env file');
44
+ }
45
+
46
+ const trimmedKey = this.apiKey.trim();
47
+ if (trimmedKey !== this.apiKey || !trimmedKey) {
48
+ throw new Error('ELEVENLABS_API_KEY contains invalid whitespace - check your .env file');
49
+ }
50
+ }
51
+
52
+ getDefaultVoiceId() {
53
+ return process.env.ELEVENLABS_VOICE_ID || DEFAULT_VOICE_ID;
54
+ }
55
+
56
+ getDefaultSettings() {
57
+ return {
58
+ voice_id: this.getDefaultVoiceId(),
59
+ model_id: process.env.ELEVENLABS_MODEL_ID || DEFAULT_MODEL_ID,
60
+ stability: DEFAULT_STABILITY,
61
+ similarity_boost: DEFAULT_SIMILARITY_BOOST
62
+ };
63
+ }
64
+
65
+ normalizeSettings(settings) {
66
+ // ElevenLabs uses its own naming convention, map common names
67
+ const normalized = { ...settings };
68
+
69
+ // Map common 'voice' to 'voice_id'
70
+ if (settings.voice && !settings.voice_id) {
71
+ normalized.voice_id = settings.voice;
72
+ delete normalized.voice;
73
+ }
74
+
75
+ // Map common 'model' to 'model_id'
76
+ if (settings.model && !settings.model_id) {
77
+ normalized.model_id = settings.model;
78
+ delete normalized.model;
79
+ }
80
+
81
+ return normalized;
82
+ }
83
+
84
+ async getVoices() {
85
+ this.validateConfig();
86
+
87
+ const response = await fetch('https://api.elevenlabs.io/v1/voices', {
88
+ headers: {
89
+ 'xi-api-key': this.apiKey.trim()
90
+ }
91
+ });
92
+
93
+ if (!response.ok) {
94
+ throw new Error(`Failed to fetch voices: ${response.status}`);
95
+ }
96
+
97
+ const data = await response.json();
98
+ return data.voices.map(v => ({
99
+ id: v.voice_id,
100
+ name: v.name,
101
+ description: v.description || v.labels?.description
102
+ }));
103
+ }
104
+
105
+ async generateAudio(text, settings = {}) {
106
+ this.validateConfig();
107
+
108
+ const normalizedSettings = this.normalizeSettings(settings);
109
+ const defaults = this.getDefaultSettings();
110
+
111
+ const voiceId = normalizedSettings.voice_id || defaults.voice_id;
112
+ const modelId = normalizedSettings.model_id || defaults.model_id;
113
+ const stability = parseFloat(normalizedSettings.stability) || defaults.stability;
114
+ const similarityBoost = parseFloat(normalizedSettings.similarity_boost) || defaults.similarity_boost;
115
+
116
+ const url = `https://api.elevenlabs.io/v1/text-to-speech/${voiceId}`;
117
+
118
+ let response;
119
+ try {
120
+ response = await fetch(url, {
121
+ method: 'POST',
122
+ headers: {
123
+ 'Accept': 'audio/mpeg',
124
+ 'Content-Type': 'application/json',
125
+ 'xi-api-key': this.apiKey.trim()
126
+ },
127
+ body: JSON.stringify({
128
+ text,
129
+ model_id: modelId,
130
+ voice_settings: {
131
+ stability,
132
+ similarity_boost: similarityBoost
133
+ }
134
+ })
135
+ });
136
+ } catch (fetchError) {
137
+ const cause = fetchError.cause ? `: ${fetchError.cause.message || fetchError.cause.code}` : '';
138
+ throw new Error(`Network error connecting to ElevenLabs API${cause}. Check your internet connection and proxy settings.`);
139
+ }
140
+
141
+ if (!response.ok) {
142
+ const errorText = await response.text();
143
+ throw new Error(`ElevenLabs API error (${response.status}): ${errorText}`);
144
+ }
145
+
146
+ return Buffer.from(await response.arrayBuffer());
147
+ }
148
+ }