From 0ee90fa9a29c9aed8438a3ad5526ca896f2b29fd Mon Sep 17 00:00:00 2001 From: Will Wade Date: Fri, 9 Jan 2026 23:14:28 +0000 Subject: [PATCH] attempt at a fix --- README.md | 2 ++ src/__tests__/speech-markdown-converter.test.ts | 11 +++++++++++ src/markdown/converter.ts | 15 ++++++++++++--- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 57500c6..48553be 100644 --- a/README.md +++ b/README.md @@ -573,6 +573,8 @@ The speechmarkdown-js library ships dedicated formatters for every major provide ### Usage +Note: ElevenLabs audio tags like `[sarcastically]` are preserved only for ElevenLabs; other engines strip them when `useSpeechMarkdown` is enabled. + ```typescript // Use Speech Markdown with any engine const markdown = diff --git a/src/__tests__/speech-markdown-converter.test.ts b/src/__tests__/speech-markdown-converter.test.ts index 8e73f29..3caed2e 100644 --- a/src/__tests__/speech-markdown-converter.test.ts +++ b/src/__tests__/speech-markdown-converter.test.ts @@ -35,6 +35,13 @@ describe("SpeechMarkdown", () => { expect(googleResult).toContain(" { + const markdown = "Hello [sarcastically] world"; + const result = await SpeechMarkdown.toSSML(markdown, "amazon-alexa"); + expect(result).not.toContain("[sarcastically]"); + expect(result).not.toContain("sarcastically"); + }); }); describe("isSpeechMarkdown", () => { @@ -62,6 +69,10 @@ describe("SpeechMarkdown", () => { expect(SpeechMarkdown.isSpeechMarkdown("Hello (loud)[volume:\"loud\"] world")).toBe(true); }); + it("should detect ElevenLabs audio tags", () => { + expect(SpeechMarkdown.isSpeechMarkdown("Hello [sarcastically] world")).toBe(true); + }); + it("should return false for plain text", () => { expect(SpeechMarkdown.isSpeechMarkdown("Hello world")).toBe(false); }); diff --git a/src/markdown/converter.ts b/src/markdown/converter.ts index e9293f5..2c4c50d 100644 --- a/src/markdown/converter.ts +++ b/src/markdown/converter.ts @@ -119,6 +119,12 @@ function convertSpeechMarkdownFallback(markdown: string): string { return out; } +const ELEVENLABS_AUDIO_TAG_PATTERN = /\[[A-Za-z][A-Za-z\s-]*\]/g; + +function stripElevenLabsAudioTags(text: string): string { + return text.replace(ELEVENLABS_AUDIO_TAG_PATTERN, ""); +} + /** * SpeechMarkdownConverter class for converting Speech Markdown to SSML */ @@ -148,19 +154,21 @@ export class SpeechMarkdownConverter { * @returns SSML text */ async toSSML(markdown: string, platform = "amazon-alexa"): Promise { + const normalized = platform === "elevenlabs" ? markdown : stripElevenLabsAudioTags(markdown); + if (!isSpeechMarkdownEnabled()) { this.speechMarkdownInstance = null; - const converted = convertSpeechMarkdownFallback(markdown); + const converted = convertSpeechMarkdownFallback(normalized); return `${converted}`; } // Attempt to initialize the full converter (no-op if disabled/unavailable) await this.ensureInitialized(); if (this.speechMarkdownInstance) { - return this.speechMarkdownInstance.toSSML(markdown, { platform }); + return this.speechMarkdownInstance.toSSML(normalized, { platform }); } // Fallback: minimal conversion - const converted = convertSpeechMarkdownFallback(markdown); + const converted = convertSpeechMarkdownFallback(normalized); return `${converted}`; } @@ -223,6 +231,7 @@ export function isSpeechMarkdown(text: string): boolean { const patterns = [ /\[\d+m?s\]/, // Breaks: [500ms] /\[break:"[^"\]]+"\]/, // Breaks with quotes: [break:"weak"] or [break:"500ms"] + /\[[A-Za-z][A-Za-z\s-]*\]/, // ElevenLabs audio tags: [sarcastically] /\+\+.*?\+\+/, // Strong emphasis: ++text++ /\+.*?\+/, // Moderate emphasis: +text+ /~.*?~/, // No emphasis: ~text~