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.
- package/LICENSE +21 -0
- package/README.md +322 -0
- package/THIRD_PARTY_NOTICES.md +22 -0
- package/bin/cli.js +331 -0
- package/framework/assets/logo-coursecode-black.svg +14 -0
- package/framework/assets/logo-coursecode-white.svg +14 -0
- package/framework/assets/logo-coursecode.svg +14 -0
- package/framework/css/01-base.css +160 -0
- package/framework/css/02-layout.css +499 -0
- package/framework/css/accessibility.css +834 -0
- package/framework/css/components/accordions.css +710 -0
- package/framework/css/components/assessments.css +520 -0
- package/framework/css/components/audio-player.css +570 -0
- package/framework/css/components/badges.css +80 -0
- package/framework/css/components/breadcrumbs.css +87 -0
- package/framework/css/components/buttons.css +707 -0
- package/framework/css/components/callouts.css +1280 -0
- package/framework/css/components/cards.css +475 -0
- package/framework/css/components/carousel.css +193 -0
- package/framework/css/components/checkbox-group.css +123 -0
- package/framework/css/components/checklist.css +203 -0
- package/framework/css/components/collapse.css +96 -0
- package/framework/css/components/comparison.css +33 -0
- package/framework/css/components/content-image.css +36 -0
- package/framework/css/components/document-gallery.css +425 -0
- package/framework/css/components/dropdown.css +115 -0
- package/framework/css/components/embed-frame.css +142 -0
- package/framework/css/components/engagement.css +412 -0
- package/framework/css/components/features.css +35 -0
- package/framework/css/components/flip-cards.css +253 -0
- package/framework/css/components/footer.css +353 -0
- package/framework/css/components/forms.css +294 -0
- package/framework/css/components/hero.css +216 -0
- package/framework/css/components/images.css +528 -0
- package/framework/css/components/interactive-timeline.css +274 -0
- package/framework/css/components/intro-cards.css +30 -0
- package/framework/css/components/lightbox.css +666 -0
- package/framework/css/components/loading.css +65 -0
- package/framework/css/components/modals.css +235 -0
- package/framework/css/components/notifications.css +107 -0
- package/framework/css/components/quote.css +150 -0
- package/framework/css/components/sidebar.css +684 -0
- package/framework/css/components/slide-header.css +52 -0
- package/framework/css/components/spinner.css +62 -0
- package/framework/css/components/stats.css +44 -0
- package/framework/css/components/steps.css +232 -0
- package/framework/css/components/tables.css +90 -0
- package/framework/css/components/tabs.css +347 -0
- package/framework/css/components/timeline.css +154 -0
- package/framework/css/components/toggle.css +95 -0
- package/framework/css/components/tooltip.css +226 -0
- package/framework/css/components/video-player.css +438 -0
- package/framework/css/design-tokens.css +707 -0
- package/framework/css/framework.css +86 -0
- package/framework/css/interactions/accessibility.css +75 -0
- package/framework/css/interactions/base.css +92 -0
- package/framework/css/interactions/drag-drop.css +295 -0
- package/framework/css/interactions/fill-in-the-blank.css +236 -0
- package/framework/css/interactions/hotspots.css +69 -0
- package/framework/css/interactions/index.css +45 -0
- package/framework/css/interactions/interactive-image.css +359 -0
- package/framework/css/interactions/likert.css +126 -0
- package/framework/css/interactions/matching.css +354 -0
- package/framework/css/interactions/numeric-input.css +78 -0
- package/framework/css/interactions/sequencing.css +378 -0
- package/framework/css/interactions/true-false.css +177 -0
- package/framework/css/layouts/article.css +258 -0
- package/framework/css/layouts/base.css +30 -0
- package/framework/css/layouts/canvas.css +38 -0
- package/framework/css/layouts/focused.css +236 -0
- package/framework/css/layouts/index.css +29 -0
- package/framework/css/layouts/presentation.css +191 -0
- package/framework/css/layouts/traditional.css +52 -0
- package/framework/css/responsive.css +439 -0
- package/framework/css/utilities/accessibility-utils.css +59 -0
- package/framework/css/utilities/animations.css +419 -0
- package/framework/css/utilities/borders.css +72 -0
- package/framework/css/utilities/colors.css +76 -0
- package/framework/css/utilities/container.css +46 -0
- package/framework/css/utilities/decorative.css +442 -0
- package/framework/css/utilities/display.css +257 -0
- package/framework/css/utilities/flexbox.css +80 -0
- package/framework/css/utilities/grid.css +69 -0
- package/framework/css/utilities/icons.css +534 -0
- package/framework/css/utilities/lists.css +190 -0
- package/framework/css/utilities/spacing.css +167 -0
- package/framework/css/utilities/tables.css +81 -0
- package/framework/css/utilities/typography.css +159 -0
- package/framework/css/utilities/visibility.css +117 -0
- package/framework/docs/COURSE_AUTHORING_GUIDE.md +1773 -0
- package/framework/docs/COURSE_OUTLINE_GUIDE.md +725 -0
- package/framework/docs/COURSE_OUTLINE_TEMPLATE.md +161 -0
- package/framework/docs/DATA_MODEL.md +409 -0
- package/framework/docs/FRAMEWORK_GUIDE.md +1088 -0
- package/framework/docs/USER_GUIDE.md +583 -0
- package/framework/docs/examples/cloudflare-channel-relay.js +169 -0
- package/framework/docs/examples/cloudflare-data-worker.js +102 -0
- package/framework/docs/examples/cloudflare-error-worker.js +228 -0
- package/framework/index.html +175 -0
- package/framework/js/app/AppActions.js +410 -0
- package/framework/js/app/AppState.js +225 -0
- package/framework/js/app/AppUI.js +616 -0
- package/framework/js/assessment/AssessmentActions.js +615 -0
- package/framework/js/assessment/AssessmentFactory.js +471 -0
- package/framework/js/assessment/AssessmentState.js +322 -0
- package/framework/js/assessment/AssessmentUI.js +451 -0
- package/framework/js/automation/api-engagement.js +196 -0
- package/framework/js/automation/api-interactions.js +167 -0
- package/framework/js/automation/api.js +242 -0
- package/framework/js/automation/index.js +41 -0
- package/framework/js/components/interactions/drag-drop.js +884 -0
- package/framework/js/components/interactions/fill-in.js +535 -0
- package/framework/js/components/interactions/hotspot.js +702 -0
- package/framework/js/components/interactions/interaction-base.js +511 -0
- package/framework/js/components/interactions/likert.js +301 -0
- package/framework/js/components/interactions/matching.js +699 -0
- package/framework/js/components/interactions/multiple-choice.js +377 -0
- package/framework/js/components/interactions/numeric.js +271 -0
- package/framework/js/components/interactions/sequencing.js +423 -0
- package/framework/js/components/interactions/true-false.js +241 -0
- package/framework/js/components/ui-components/accordion.js +442 -0
- package/framework/js/components/ui-components/alert.js +88 -0
- package/framework/js/components/ui-components/audio-player.js +1193 -0
- package/framework/js/components/ui-components/callout.js +121 -0
- package/framework/js/components/ui-components/carousel.js +145 -0
- package/framework/js/components/ui-components/checkbox-group.js +87 -0
- package/framework/js/components/ui-components/checklist.js +40 -0
- package/framework/js/components/ui-components/collapse.js +114 -0
- package/framework/js/components/ui-components/comparison.js +30 -0
- package/framework/js/components/ui-components/conditional-display.js +150 -0
- package/framework/js/components/ui-components/content-image.js +41 -0
- package/framework/js/components/ui-components/dropdown.js +262 -0
- package/framework/js/components/ui-components/embed-frame.js +274 -0
- package/framework/js/components/ui-components/features.js +33 -0
- package/framework/js/components/ui-components/flip-card.js +230 -0
- package/framework/js/components/ui-components/form-validator.js +76 -0
- package/framework/js/components/ui-components/hero.js +49 -0
- package/framework/js/components/ui-components/index.js +12 -0
- package/framework/js/components/ui-components/interactive-image.js +235 -0
- package/framework/js/components/ui-components/interactive-timeline.js +285 -0
- package/framework/js/components/ui-components/intro-cards.js +35 -0
- package/framework/js/components/ui-components/lightbox.js +652 -0
- package/framework/js/components/ui-components/modal.js +386 -0
- package/framework/js/components/ui-components/notifications.js +145 -0
- package/framework/js/components/ui-components/progress.js +88 -0
- package/framework/js/components/ui-components/quote.js +41 -0
- package/framework/js/components/ui-components/stats.js +33 -0
- package/framework/js/components/ui-components/steps.js +41 -0
- package/framework/js/components/ui-components/tabs.js +255 -0
- package/framework/js/components/ui-components/timeline.js +42 -0
- package/framework/js/components/ui-components/toggle-group.js +73 -0
- package/framework/js/components/ui-components/tooltip.js +458 -0
- package/framework/js/components/ui-components/value-display.js +133 -0
- package/framework/js/components/ui-components/video-player.js +686 -0
- package/framework/js/core/component-catalog.js +121 -0
- package/framework/js/core/event-bus.js +178 -0
- package/framework/js/core/interaction-catalog.js +149 -0
- package/framework/js/dev/runtime-linter.js +1725 -0
- package/framework/js/drivers/cmi5-driver.js +768 -0
- package/framework/js/drivers/driver-factory.js +77 -0
- package/framework/js/drivers/driver-interface.js +110 -0
- package/framework/js/drivers/http-driver-base.js +241 -0
- package/framework/js/drivers/lti-driver.js +508 -0
- package/framework/js/drivers/proxy-driver.js +444 -0
- package/framework/js/drivers/scorm-12-driver.js +560 -0
- package/framework/js/drivers/scorm-2004-driver.js +775 -0
- package/framework/js/drivers/scorm-driver-base.js +112 -0
- package/framework/js/engagement/engagement-manager.js +404 -0
- package/framework/js/engagement/engagement-progress.js +191 -0
- package/framework/js/engagement/engagement-trackers.js +215 -0
- package/framework/js/engagement/requirement-strategies.js +268 -0
- package/framework/js/main.js +727 -0
- package/framework/js/managers/accessibility-manager.js +499 -0
- package/framework/js/managers/assessment-manager.js +230 -0
- package/framework/js/managers/audio-manager.js +944 -0
- package/framework/js/managers/comment-manager.js +88 -0
- package/framework/js/managers/flag-manager.js +86 -0
- package/framework/js/managers/interaction-manager.js +254 -0
- package/framework/js/managers/interaction-registry.js +96 -0
- package/framework/js/managers/objective-manager.js +423 -0
- package/framework/js/managers/score-manager.js +441 -0
- package/framework/js/managers/video-manager.js +536 -0
- package/framework/js/navigation/Breadcrumbs.js +234 -0
- package/framework/js/navigation/NavigationActions.js +1132 -0
- package/framework/js/navigation/NavigationState.js +276 -0
- package/framework/js/navigation/NavigationUI.js +574 -0
- package/framework/js/navigation/document-gallery.js +357 -0
- package/framework/js/navigation/navigation-helpers.js +175 -0
- package/framework/js/navigation/navigation-validators.js +174 -0
- package/framework/js/state/index.js +8 -0
- package/framework/js/state/lms-connection.js +482 -0
- package/framework/js/state/lms-error-utils.js +58 -0
- package/framework/js/state/state-commits.js +200 -0
- package/framework/js/state/state-domains.js +86 -0
- package/framework/js/state/state-manager.js +502 -0
- package/framework/js/state/state-validation.js +311 -0
- package/framework/js/state/transaction-log.js +41 -0
- package/framework/js/state/xapi-statement-service.js +325 -0
- package/framework/js/utilities/access-control.js +99 -0
- package/framework/js/utilities/breakpoint-manager.js +315 -0
- package/framework/js/utilities/canvas-slide.js +35 -0
- package/framework/js/utilities/conditional-display.js +388 -0
- package/framework/js/utilities/course-channel.js +214 -0
- package/framework/js/utilities/course-helpers.js +420 -0
- package/framework/js/utilities/data-reporter.js +273 -0
- package/framework/js/utilities/error-reporter.js +313 -0
- package/framework/js/utilities/hotspot-helper.js +341 -0
- package/framework/js/utilities/icons.js +348 -0
- package/framework/js/utilities/logger.js +92 -0
- package/framework/js/utilities/markdown-renderer.js +45 -0
- package/framework/js/utilities/scroll-tracker.js +68 -0
- package/framework/js/utilities/ui-initializer.js +146 -0
- package/framework/js/utilities/utilities.js +293 -0
- package/framework/js/utilities/view-manager.js +227 -0
- package/framework/js/validation/html-validators.js +422 -0
- package/framework/js/validation/scorm-validators.js +438 -0
- package/framework/js/vendor/pipwerks.js +931 -0
- package/framework/scripts/generate-narration.js +629 -0
- package/framework/scripts/tts-providers/azure-provider.js +178 -0
- package/framework/scripts/tts-providers/base-provider.js +81 -0
- package/framework/scripts/tts-providers/deepgram-provider.js +135 -0
- package/framework/scripts/tts-providers/elevenlabs-provider.js +148 -0
- package/framework/scripts/tts-providers/google-provider.js +272 -0
- package/framework/scripts/tts-providers/index.js +158 -0
- package/framework/scripts/tts-providers/openai-provider.js +143 -0
- package/framework/version.json +63 -0
- package/lib/authoring-api.js +919 -0
- package/lib/build-linter.js +450 -0
- package/lib/build-packaging.js +186 -0
- package/lib/build.js +88 -0
- package/lib/cloud.js +691 -0
- package/lib/convert.js +341 -0
- package/lib/course-parser.js +936 -0
- package/lib/course-writer.js +258 -0
- package/lib/create.js +248 -0
- package/lib/css-index.js +237 -0
- package/lib/dev.js +51 -0
- package/lib/export-content.js +1246 -0
- package/lib/headless-browser.js +413 -0
- package/lib/import.js +377 -0
- package/lib/index.js +80 -0
- package/lib/info.js +79 -0
- package/lib/interaction-formatters.js +568 -0
- package/lib/manifest/cmi5-manifest.js +63 -0
- package/lib/manifest/lti-tool-config.js +53 -0
- package/lib/manifest/manifest-factory.js +99 -0
- package/lib/manifest/scorm-12-manifest.js +61 -0
- package/lib/manifest/scorm-2004-manifest.js +94 -0
- package/lib/manifest/scorm-proxy-manifest.js +104 -0
- package/lib/manifest-parser.js +96 -0
- package/lib/mcp-prompts.js +753 -0
- package/lib/mcp-server.js +316 -0
- package/lib/narration.js +53 -0
- package/lib/pdf-structure.js +142 -0
- package/lib/preview-export.js +231 -0
- package/lib/preview-routes-api.js +662 -0
- package/lib/preview-routes-editing.js +159 -0
- package/lib/preview-routes-lms.js +230 -0
- package/lib/preview-server.js +564 -0
- package/lib/project-utils.js +269 -0
- package/lib/proxy-templates/proxy.html +68 -0
- package/lib/proxy-templates/scorm-bridge.js +112 -0
- package/lib/scaffold.js +193 -0
- package/lib/schema-extractor.js +361 -0
- package/lib/slide-source-editor.js +586 -0
- package/lib/stub-player/app-viewer.js +195 -0
- package/lib/stub-player/app.js +370 -0
- package/lib/stub-player/catalog-panel.js +312 -0
- package/lib/stub-player/config-panel.js +1303 -0
- package/lib/stub-player/content-generator.js +586 -0
- package/lib/stub-player/content-viewer.js +173 -0
- package/lib/stub-player/debug-panel.js +420 -0
- package/lib/stub-player/edit-mode.js +922 -0
- package/lib/stub-player/edit-utils.js +400 -0
- package/lib/stub-player/header-bar.js +354 -0
- package/lib/stub-player/interaction-editor.js +210 -0
- package/lib/stub-player/interactions-panel.js +565 -0
- package/lib/stub-player/lms-api.js +1094 -0
- package/lib/stub-player/login-screen.js +74 -0
- package/lib/stub-player/outline-mode.js +689 -0
- package/lib/stub-player/styles/_assessments-panel.css +245 -0
- package/lib/stub-player/styles/_base.css +89 -0
- package/lib/stub-player/styles/_catalog-icons.css +96 -0
- package/lib/stub-player/styles/_catalog-panel.css +291 -0
- package/lib/stub-player/styles/_config-panel.css +636 -0
- package/lib/stub-player/styles/_content-viewer.css +834 -0
- package/lib/stub-player/styles/_debug-panel.css +576 -0
- package/lib/stub-player/styles/_edit-mode.css +128 -0
- package/lib/stub-player/styles/_header-bar.css +343 -0
- package/lib/stub-player/styles/_interaction-editor.css +140 -0
- package/lib/stub-player/styles/_interactions-panel.css +1038 -0
- package/lib/stub-player/styles/_login-screen.css +102 -0
- package/lib/stub-player/styles/_outline-mode.css +752 -0
- package/lib/stub-player/styles.css +15 -0
- package/lib/stub-player.js +160 -0
- package/lib/test-data-reporting.js +176 -0
- package/lib/test-error-reporting.js +146 -0
- package/lib/token.js +86 -0
- package/lib/upgrade.js +257 -0
- package/lib/validation-rules.js +517 -0
- package/lib/vite-plugin-content-discovery.js +296 -0
- package/package.json +108 -0
- package/schemas/XMLSchema.dtd +402 -0
- package/schemas/adlcp_v1p3.xsd +111 -0
- package/schemas/adlnav_v1p3.xsd +61 -0
- package/schemas/adlseq_v1p3.xsd +93 -0
- package/schemas/common/anyElement.xsd +27 -0
- package/schemas/common/dataTypes.xsd +138 -0
- package/schemas/common/elementNames.xsd +767 -0
- package/schemas/common/elementTypes.xsd +786 -0
- package/schemas/common/rootElement.xsd +31 -0
- package/schemas/common/vocabTypes.xsd +345 -0
- package/schemas/common/vocabValues.xsd +257 -0
- package/schemas/datatypes.dtd +203 -0
- package/schemas/ims_xml.xsd +35 -0
- package/schemas/imscp_v1p1.xsd +368 -0
- package/schemas/imsss_v1p0.xsd +67 -0
- package/schemas/imsss_v1p0auxresource.xsd +19 -0
- package/schemas/imsss_v1p0control.xsd +20 -0
- package/schemas/imsss_v1p0delivery.xsd +17 -0
- package/schemas/imsss_v1p0limit.xsd +47 -0
- package/schemas/imsss_v1p0objective.xsd +67 -0
- package/schemas/imsss_v1p0random.xsd +16 -0
- package/schemas/imsss_v1p0rollup.xsd +46 -0
- package/schemas/imsss_v1p0seqrule.xsd +108 -0
- package/schemas/imsss_v1p0util.xsd +94 -0
- package/schemas/license.txt +17 -0
- package/schemas/lom.xsd +102 -0
- package/schemas/lomCustom.xsd +62 -0
- package/schemas/lomLoose.xsd +62 -0
- package/schemas/lomStrict.xsd +62 -0
- package/schemas/xml.xsd +81 -0
- package/template/.env.example +92 -0
- package/template/course/assets/audio/example-intro.mp3 +0 -0
- package/template/course/assets/audio/example-ui-demo--compact-player.mp3 +0 -0
- package/template/course/assets/audio/example-ui-demo--demo-modal.mp3 +0 -0
- package/template/course/assets/audio/example-ui-demo--full-player.mp3 +0 -0
- package/template/course/assets/docs/example_md_1.md +39 -0
- package/template/course/assets/docs/example_md_2.md +41 -0
- package/template/course/assets/docs/example_pdf_1_thumbnail.png +0 -0
- package/template/course/assets/docs/example_pdf_2.pdf +0 -0
- package/template/course/assets/images/course-architecture.svg +36 -0
- package/template/course/assets/images/logo.svg +14 -0
- package/template/course/assets/widgets/counter-demo.html +190 -0
- package/template/course/assets/widgets/gravity-painter.html +384 -0
- package/template/course/course-config.js +539 -0
- package/template/course/icons.js +19 -0
- package/template/course/interactions/PLUGIN_GUIDE.md +97 -0
- package/template/course/slides/example-course-structure.js +138 -0
- package/template/course/slides/example-final-exam.js +144 -0
- package/template/course/slides/example-finishing.js +127 -0
- package/template/course/slides/example-interactions-showcase.js +615 -0
- package/template/course/slides/example-preview-tour.js +129 -0
- package/template/course/slides/example-remedial.js +143 -0
- package/template/course/slides/example-summary.js +103 -0
- package/template/course/slides/example-ui-showcase.js +1805 -0
- package/template/course/slides/example-welcome.js +123 -0
- package/template/course/slides/example-workflow.js +140 -0
- package/template/course/theme.css +165 -0
- package/template/eslint.config.js +47 -0
- package/template/package.json +28 -0
- 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, '&')
|
|
140
|
+
.replace(/</g, '<')
|
|
141
|
+
.replace(/>/g, '>')
|
|
142
|
+
.replace(/"/g, '"')
|
|
143
|
+
.replace(/'/g, ''');
|
|
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
|
+
}
|