{"version":3,"sources":["jsdelivr-header.js","/npm/edge-tts-generator@0.1.7/src/index.ts","/npm/edge-tts-generator@0.1.7/src/constants.ts","/npm/edge-tts-generator@0.1.7/src/edge-tts.ts","/npm/edge-tts-generator@0.1.7/src/generate-mp3.ts","/npm/edge-tts-generator@0.1.7/src/utils.ts"],"names":[],"mappings":"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","file":"/npm/edge-tts-generator@0.1.7/dist/index.js","sourceRoot":"","sourcesContent":["/**\n * Minified by jsDelivr using Terser v5.39.0.\n * Original file: /npm/edge-tts-generator@0.1.7/dist/index.js\n *\n * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files\n */\n","export * from './constants';\nexport * from './edge-tts';\nexport * from './generate-mp3';","export enum OUTPUT_FORMAT {\n  AUDIO_24KHZ_48KBITRATE_MONO_MP3 = \"audio-24khz-48kbitrate-mono-mp3\",\n  AUDIO_24KHZ_96KBITRATE_MONO_MP3 = \"audio-24khz-96kbitrate-mono-mp3\",\n  WEBM_24KHZ_16BIT_MONO_OPUS = \"webm-24khz-16bit-mono-opus\",\n}\n\nexport enum PITCH {\n  X_LOW = \"x-low\",\n  LOW = \"low\",\n  MEDIUM = \"medium\",\n  HIGH = \"high\",\n  X_HIGH = \"x-high\",\n  DEFAULT = \"default\",\n}\n\nexport enum RATE {\n  X_SLOW = \"x-slow\",\n  SLOW = \"slow\",\n  MEDIUM = \"medium\",\n  FAST = \"fast\",\n  X_FAST = \"x-fast\",\n  DEFAULT = \"default\",\n}\n\nexport enum VOLUME {\n  SILENT = \"silent\",\n  X_SOFT = \"x-soft\",\n  SOFT = \"soft\",\n  MEDIUM = \"medium\",\n  LOUD = \"loud\",\n  X_LOUD = \"x-LOUD\",\n  DEFAULT = \"default\",\n}\n","import { Buffer } from 'buffer';\nimport type { PITCH, RATE, VOLUME } from './constants';\nimport { OUTPUT_FORMAT } from './constants';\nimport { WebSocket } from 'ws';\nimport fetch from 'node-fetch';\nimport { randomBytes } from 'crypto';\nimport { TextEncoder } from 'util';\n\n// Generates a random hex string of the specified length\nfunction generateRandomHex(length: number): string {\n  const randomBuffer = randomBytes(length);\n  return randomBuffer.toString('hex');\n}\n\ntype EventType = \"data\" | \"close\" | \"end\" | \"error\";\n\nclass EventEmitter {\n  private eventListeners: Record<EventType, ((...args: any[]) => void)[]>;\n\n  constructor() {\n    this.eventListeners = { data: [], close: [], end: [], error: [] };\n  }\n\n  on(event: EventType, callback: (...args: any[]) => void) {\n    this.eventListeners[event].push(callback);\n  }\n\n  emit(event: EventType, data: any) {\n    this.eventListeners[event].forEach((callback) => callback(data));\n  }\n}\n\nexport type Voice = {\n  Name: string;\n  ShortName: string;\n  Gender: string;\n  Locale: string;\n  SuggestedCodec: string;\n  FriendlyName: string;\n  Status: string;\n};\n\ntype Metadata = {\n  Type: \"WordBoundary\" | \"SentenceBoundary\";\n  Data: {\n    Offset: number;\n    Duration: number;\n    text: {\n      Text: string;\n      Length: number;\n      BoundaryType: \"WordBoundary\" | \"SentenceBoundary\";\n    };\n  };\n}[];\n\nexport class ProsodyOptions {\n  pitch: PITCH | string = \"+0Hz\";\n  rate: RATE | string | number = 1.0;\n  volume: VOLUME | string | number = 100.0;\n}\n\nexport class EdgeTTSClient {\n  static OUTPUT_FORMAT = OUTPUT_FORMAT;\n  private static CLIENT_TOKEN = \"6A5AA1D4EAFF4E9FB37E23D68491D6F4\";\n  private static VOICES_URL = `https://speech.platform.bing.com/consumer/speech/synthesize/readaloud/voices/list?trustedclienttoken=${EdgeTTSClient.CLIENT_TOKEN}`;\n  private static SYNTH_URL = `wss://speech.platform.bing.com/consumer/speech/synthesize/readaloud/edge/v1?TrustedClientToken=${EdgeTTSClient.CLIENT_TOKEN}`;\n  private static BINARY_DELIM = \"Path:audio\\r\\n\";\n  private static VOICE_LANG_REGEX = /\\w{2}-\\w{2}/;\n\n  private enableLogging: boolean;\n  private ws: WebSocket | null = null;\n  private voice: string | null = null;\n  private voiceLocale: string | null = null;\n  private outputFormat: OUTPUT_FORMAT | null = null;\n  private requestQueue: Record<string, EventEmitter> = {};\n  private connectionStartTime = 0;\n\n  constructor(enableLogging = false) {\n    this.enableLogging = enableLogging;\n  }\n\n  private log(...args: any[]) {\n    if (this.enableLogging) console.log(...args);\n  }\n\n  private async sendMessage(message: string) {\n    for (let attempt = 1; attempt <= 3 && (this.ws === null || this.ws.readyState !== WebSocket.OPEN); attempt++) {\n      if (attempt === 1) this.connectionStartTime = Date.now();\n      this.log(`Connecting... attempt ${attempt}`);\n      await this.initWebSocket();\n    }\n    this.ws?.send(message);\n  }\n\n  private initWebSocket() {\n    this.ws = new WebSocket(EdgeTTSClient.SYNTH_URL);\n    this.ws.binaryType = \"arraybuffer\";\n    let metadataBuffer: Metadata = [];\n\n    return new Promise<void>((resolve, reject) => {\n      this.ws!.onopen = () => {\n        this.log(\"Connected in\", (Date.now() - this.connectionStartTime) / 1000, \"seconds\");\n        this.sendMessage(this.getConfigMessage()).then(resolve);\n      };\n\n      this.ws!.onmessage = (event) => this.handleMessage(event, metadataBuffer);\n      this.ws!.onclose = () => this.handleClose();\n      this.ws!.onerror = (error: any) => reject(`Connection Error: ${error.message}`);\n    });\n  }\n\n  private handleMessage(event: any, metadataBuffer: Metadata) {\n    const buffer = Buffer.from(event.data as ArrayBuffer);\n    const message = buffer.toString();\n    const requestIdMatch = /X-RequestId:(.*?)\\r\\n/.exec(message);\n    const requestId = requestIdMatch ? requestIdMatch[1] : \"\";\n\n    if (message.includes(\"Path:turn.start\")) {\n      metadataBuffer.length = 0;\n    } else if (message.includes(\"Path:turn.end\")) {\n      this.requestQueue[requestId]?.emit(\"end\", metadataBuffer);\n    } else if (message.includes(\"Path:audio\")) {\n      this.cacheAudioData(buffer, requestId);\n    } else if (message.includes(\"Path:audio.metadata\")) {\n      const startIndex = message.indexOf(\"{\");\n      metadataBuffer.push(JSON.parse(message.slice(startIndex)).Metadata[0]);\n    } else {\n      this.log(\"Unknown Message\", message);\n    }\n  }\n\n  private handleClose() {\n    this.log(\"Disconnected after:\", (Date.now() - this.connectionStartTime) / 1000, \"seconds\");\n    for (const requestId in this.requestQueue) {\n      this.requestQueue[requestId].emit(\"close\", null);\n    }\n  }\n\n  private cacheAudioData(buffer: Buffer, requestId: string) {\n    // Convert the BINARY_DELIM string to a Uint8Array using TextEncoder\n    const binaryDelimBytes = new TextEncoder().encode(EdgeTTSClient.BINARY_DELIM);\n\n    // Use the helper function to find the delimiter index in the buffer\n    const delimiterIndex = this.findDelimiterIndex(buffer, binaryDelimBytes);\n    if (delimiterIndex === -1) {\n      this.log('Delimiter not found in the buffer.');\n      return;\n    }\n\n    const audioDataStart = delimiterIndex + binaryDelimBytes.length;\n    const audioData = buffer.subarray(audioDataStart); // Use subarray for Buffer\n    this.requestQueue[requestId]?.emit(\"data\", audioData);\n    this.log(\"Received audio chunk of size:\", audioData?.length);\n  }\n\n  // Helper function to find the index of a byte sequence within another byte sequence\n  private findDelimiterIndex(buffer: Buffer, delimiter: Uint8Array): number {\n    for (let i = 0; i <= buffer.length - delimiter.length; i++) {\n      let match = true;\n      for (let j = 0; j < delimiter.length; j++) {\n        if (buffer[i + j] !== delimiter[j]) {\n          match = false;\n          break;\n        }\n      }\n      if (match) return i;\n    }\n    return -1;\n  }\n\n  private getConfigMessage(): string {\n    return `Content-Type:application/json; charset=utf-8\\r\\nPath:speech.config\\r\\n\\r\\n{\n            \"context\": {\n                \"synthesis\": {\n                    \"audio\": {\n                        \"metadataoptions\": {\n                            \"sentenceBoundaryEnabled\": \"true\",\n                            \"wordBoundaryEnabled\": \"true\"\n                        },\n                        \"outputFormat\": \"${this.outputFormat}\"\n                    }\n                }\n            }\n        }`;\n  }\n\n  async getVoices(): Promise<Voice[]> {\n    try {\n      const response = await fetch(EdgeTTSClient.VOICES_URL);\n      if (!response.ok) {\n        throw new Error(`HTTP error! status: ${response.status}`);\n      }\n      return await response.json() as Voice[];\n    } catch (error) {\n      return Promise.reject(error);\n    }\n  }\n\n  async setMetadata(voiceName: string, outputFormat: OUTPUT_FORMAT, voiceLocale?: string) {\n    this.voice = voiceName;\n    this.outputFormat = outputFormat;\n    this.voiceLocale = voiceLocale || this.inferLocaleFromVoiceName(voiceName);\n\n    if (!this.voiceLocale) {\n      throw new Error(\"Could not infer voiceLocale from voiceName!\");\n    }\n\n    if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\n      this.connectionStartTime = Date.now();\n      await this.initWebSocket();\n    }\n  }\n\n  private inferLocaleFromVoiceName(voiceName: string): string | null {\n    const match = EdgeTTSClient.VOICE_LANG_REGEX.exec(voiceName);\n    return match ? match[0] : null;\n  }\n\n  close() {\n    this.ws?.close();\n  }\n\n  toStream(text: string, options: ProsodyOptions = new ProsodyOptions()): EventEmitter {\n    return this.sendSSMLRequest(this.buildSSML(text, options));\n  }\n\n  private buildSSML(text: string, options: ProsodyOptions): string {\n    return `<speak version=\"1.0\" xmlns=\"http://www.w3.org/2001/10/synthesis\" xmlns:mstts=\"https://www.w3.org/2001/mstts\" xml:lang=\"${this.voiceLocale}\">\n            <voice name=\"${this.voice}\">\n                <prosody pitch=\"${options.pitch}\" rate=\"${options.rate}\" volume=\"${options.volume}\">\n                    ${text}\n                </prosody>\n            </voice>\n        </speak>`;\n  }\n\n  private sendSSMLRequest(ssml: string): EventEmitter {\n    if (!this.ws) {\n      throw new Error(\"WebSocket not initialized. Call setMetadata first.\");\n    }\n\n    const requestId = generateRandomHex(16);\n    const requestMessage = `X-RequestId:${requestId}\\r\\nContent-Type:application/ssml+xml\\r\\nPath:ssml\\r\\n\\r\\n${ssml.trim()}`;\n\n    const eventEmitter = new EventEmitter();\n    this.requestQueue[requestId] = eventEmitter;\n    this.sendMessage(requestMessage).then();\n\n    return eventEmitter;\n  }\n}","import * as fs from 'fs/promises';\nimport * as path from 'path';\nimport { EdgeTTSClient, ProsodyOptions, Voice } from './edge-tts';\nimport { OUTPUT_FORMAT } from './constants';\nimport { filterMarkdown } from './utils';\n\nconst DEFAULT_OPTIONS: TextToSpeechOptions = {\n  voice: 'en-GB-RyanNeural',\n  speed: 1.1,\n  enableLogging: false,\n  disableFilter: false,\n}\n\nexport type TextToSpeechOptions = {\n  voice?: string;\n  speed?: number;\n  enableLogging?: boolean;\n  disableFilter?: boolean;\n}\n\nexport type TextToSpeechProps = {\n  text: string;\n  outputPath: string;\n  fileName: string;\n  options?: TextToSpeechOptions;\n}\n\nexport async function textToSpeechMp3({\n  text,\n  outputPath,\n  fileName,\n  options = DEFAULT_OPTIONS,\n}: TextToSpeechProps): Promise<void> {\n\n  const client = new EdgeTTSClient(options.enableLogging);\n\n  try {\n    // Ensure fileName ends with .mp3\n    const finalFileName = fileName.toLowerCase().endsWith('.mp3')\n      ? fileName\n      : `${fileName}.mp3`;\n\n    // Combine directory path and filename\n    const fullOutputPath = path.join(outputPath, finalFileName);\n\n    // Create output directory if it doesn't exist\n    await fs.mkdir(outputPath, { recursive: true });\n\n    // Not necessary\n    // const voices = await client.getVoices();\n    // const voiceSelection: Voice | undefined = voices.find(v => v.ShortName === options.voice);\n\n    // if (!voiceSelection) {\n    //   throw new Error(`Voice with short name \"${voiceSelection}\" not found.`);\n    // }\n\n    await client.setMetadata(options.voice, OUTPUT_FORMAT.AUDIO_24KHZ_48KBITRATE_MONO_MP3);\n\n    const prosodyOptions = new ProsodyOptions();\n    prosodyOptions.rate = options.speed ?? DEFAULT_OPTIONS.speed;\n\n    if (!options.disableFilter) {\n      text = filterMarkdown(text);\n    }\n\n    const stream = client.toStream(text, prosodyOptions);\n\n    const chunks: Buffer[] = [];\n    stream.on('data', (chunk: Buffer) => {\n      chunks.push(chunk);\n    });\n\n    return new Promise<void>((resolve, reject) => {\n      stream.on('end', async () => {\n        const audioBuffer = Buffer.concat(chunks);\n\n        try {\n          await fs.writeFile(fullOutputPath, audioBuffer);\n          console.log(`Audio saved to ${fullOutputPath}`);\n          resolve();\n        } catch (err) {\n          console.error('Error writing audio to file:', err);\n          reject(err);\n        } finally {\n          client.close();\n        }\n      });\n\n      stream.on('error', (error: any) => {\n        console.error('Stream error:', error);\n        client.close();\n        reject(error);\n      });\n    });\n\n  } catch (error) {\n    console.error('Error during text-to-speech:', error);\n    client.close();\n    throw error;\n  }\n}\n\nexport type TextToSpeechInput = {\n  text: string;\n  title: string;\n  options?: TextToSpeechOptions;\n}\n\nexport async function batchTextToSpeechMp3(\n  inputs: TextToSpeechInput[],\n  outputPath: string,\n  globalOptions: TextToSpeechOptions = DEFAULT_OPTIONS\n): Promise<void> {\n  // Create output directory if it doesn't exist\n  await fs.mkdir(outputPath, { recursive: true });\n\n  // Process all inputs sequentially to avoid overwhelming the TTS service\n  for (const input of inputs) {\n    try {\n      await textToSpeechMp3({\n        text: input.text,\n        outputPath,\n        fileName: input.title,\n        options: {\n          ...globalOptions,\n          ...input.options || {},\n        }\n      });\n    } catch (error) {\n      console.error(`Failed to process \"${input.title}\":`, error);\n      // Continue with next item even if one fails\n    }\n  }\n}\n\n// Example usage:\n// async function runBatchExample() {\n//   const inputs: TextToSpeechInput[] = [\n//     {\n//       text: \"This is the first audio file\",\n//       title: \"first-audio\",\n//       options: { voice: 'en-US-GuyNeural', speed: 1.0 }\n//     },\n//     {\n//       text: \"This is the second audio file\",\n//       title: \"second-audio\"\n//     }\n//   ];\n//\n//   const outputDir = './output';\n//\n//   try {\n//     await batchTextToSpeechMp3(\n//       inputs,\n//       outputDir,\n//       { \n//         voice: 'en-GB-RyanNeural',\n//         speed: 1.2,\n//         enableLogging: true \n//       }\n//     );\n//     console.log('Batch text-to-speech conversion completed!');\n//   } catch (error) {\n//     console.error('Batch conversion failed:', error);\n//   }\n// }","/* eslint-disable @typescript-eslint/no-inferrable-types */\n/* eslint-disable no-useless-escape */\n\n/**\n * Filters out the frontmatter from a Markdown text.\n * Frontmatter is defined as a block of YAML enclosed by `---` or `...` at the start of the file.\n * @param text - The Markdown text from which to remove frontmatter.\n * @returns The text without the frontmatter block.\n */\nexport function filterFrontmatter(text: string): string {\n  // Define the regular expression for frontmatter blocks\n  const frontmatterRegex = /^(---|\\.\\.\\.)([\\s\\S]*?)\\1\\n?/;\n\n  // Remove frontmatter if it exists\n  return text.replace(frontmatterRegex, '').trim();\n}\n\nexport function replaceComparisonSymbols(text: string): string {\n  return text\n    .replace(/>=/g, '≥') // Replace \">=\" with \"≥\"\n    .replace(/<=/g, '≤'); // Replace \"<=\" with \"≤\"\n}\n\nexport function escapeAmpersand(text: string): string {\n  return text.replace(/&/g, '&amp;')  // Escape '&'\n}\n\nexport function escapeXml(text: string): string {\n  return text\n    // .replace(/&/g, '&amp;')  // Escape '&' first to avoid double escaping\n    .replace(/</g, '&lt;')   // Escape '<'\n    .replace(/>/g, '&gt;')   // Escape '>'\n    .replace(/\"/g, '&quot;') // Escape '\"'\n    .replace(/'/g, '&apos;'); // Escape \"'\"\n}\n\nexport function filterMarkdown(text: string, overrideAmpersandEscape = false): string {\n  // Remove frontmatter (e.g., YAML between triple dashes \"---\")\n  const noFrontmatter = text.replace(/^-{3}[\\s\\S]*?-{3}\\n?/, '');\n\n  // Remove URLs\n  const noUrls = noFrontmatter.replace(/https?:\\/\\/[^\\s]+/g, '');\n\n  // Remove code blocks (e.g., fenced with ``` or indented by 4 spaces)\n  const noCodeBlocks = noUrls.replace(/```[\\s\\S]*?```/g, '').replace(/^( {4}|\\t).+/gm, '');\n\n  // Remove inline markdown syntax\n  let cleanedMarkdown = noCodeBlocks\n    // Remove bold (**text** or __text__) and italics (*text* or _text_)\n    .replace(/(\\*\\*|__)(.*?)\\1/g, '$2') // Bold\n    .replace(/(\\*|_)(.*?)\\1/g, '$2')   // Italics\n    // Remove inline code markers (`code`)\n    .replace(/`([^`]*)`/g, '$1')\n    // Remove strikethrough (~~text~~)\n    .replace(/~~(.*?)~~/g, '$1')\n    // Remove other markdown characters (e.g., #, -, *, etc., not part of inline code)\n    .replace(/^[#*-]+\\s*/gm, '')\n    // Remove unordered list markers (e.g., \"- \", \"* \", \"+ \")\n    .replace(/^[\\-\\+\\*]\\s+/gm, '')\n    // Remove ordered list numbers (e.g., \"1. \", \"2. \")\n    .replace(/^\\d+\\.\\s+/gm, '')\n    // Remove blockquote markers (e.g., \"> \") only at the start of a line\n    .replace(/^>\\s+/gm, '')\n    // Remove horizontal rules (e.g., \"---\", \"***\")\n    .replace(/^[-*]{3,}\\s*$/gm, '');\n\n  cleanedMarkdown = replaceComparisonSymbols(cleanedMarkdown);\n\n  // Remove HTML tags explicitly while preserving `<` and `>` symbols in text\n  cleanedMarkdown = cleanedMarkdown.replace(/<([^>\\s]+)[^>]*>/g, '');\n\n  // Trim leading and trailing whitespace and escape '&' if indicated\n  cleanedMarkdown = (overrideAmpersandEscape) ? cleanedMarkdown.trim() : escapeAmpersand(cleanedMarkdown.trim());\n\n  const finalText = escapeXml(cleanedMarkdown);\n\n  return finalText;\n}"]}