npm package: @lucifinil/tts-editor
Description: ## 介绍
Author: https://github.com/lucifer9531
Latest version: 1.1.9

# tts-editor

## 介绍

- 提供多音、文本、数值、英文、停顿等五种方式的划词选择。
  ![1724981348142](https://s2.loli.net/2024/08/30/kBnPmMHRy35YKcr.jpg)
  ![1724981436277](https://s2.loli.net/2024/08/30/cSouYpFAtfemDXr.jpg)

### 多音

- 对汉字的处理，不同的汉字有多种读音或音调，可对该字指定读法。

### 文本

- 提供人名、地址、车牌号、标点符号、ID、日期、时间、计量单位等，指定该文本按照什么形式读。

### 数值

- 提供读数值、读数字、读手机等。

### 英文

- 提供读字母和读单词。

### 停顿

- 在某处可停顿 0.5s、1s、2s 等

### 插入变量

- 支持在任意位置插入变量，并点选变量的交互方式给变量设置其标签
- 支持设置文本和数值下的所有标签

## Install

```bash
  npm i @lucifinil/tts-editor
```

## Usages

```vue
<template>
  <div>
    <tts-editor
      :ref="`ssml${index}`"
      :text="show_list[index].value"
      :richText="show_list[index].richText"
      @ssmlGenerated="ssmlGenerated($event, index)"
      @playAudio="playAudio($event, index)"
      @input="changeInput($event, index)"
    >
      <template #btn>
        <a-button type="primary" size="small">生成素材</a-button>
      </template>
    </tts-editor>
  </div>
</template>

<script>
import TtsEditor from '@lucifinil/tts-editor'
export default {
  name: 'tts',
  components: {
    TtsEditor
  },
  data() {
    return {
      text: '新鲜的微风666吹动我的衣袂，如同爱人的鼻息吹着我的手一样。',
      show_list: []
    }
  },
  methods: {
    ssmlGenerated(ssml, index) {
      this.show_list[index] = {
        ...this.show_list[index],
        ssml
      }
      this.ssmlList[index] = ssml
    },
    changeInput(value, index) {
      this.show_list[index] = {
        ...this.show_list[index],
        value
      }
      const domRef = this.$refs[`ssml${index}`][0]?.$el
      const targetElement = domRef?.querySelector('.tts-editor-content')
      this.show_list[index].richText = this.htmlSerializer(targetElement)
    },
    htmlSerializer(targetElement) {
      if (!targetElement) return
      const contentChildren = Array.from(targetElement?.childNodes)
      const clone = document.createElement('div')
      contentChildren.forEach(child => {
        clone.appendChild(child.cloneNode(true))
      })
      const serializer = new XMLSerializer()
      return serializer.serializeToString(clone)
    },
    async getAudioPath(index) {
      try {
        const {value} = this.show_list[index]
        const {data} = await this.$robot.getBroadcast({
          text: `<speak>${value}</speak>`,
          voice: 'aixia',
          volume: 50,
          speechRate: -80,
          vendor: 2,
          cloudType: 1
        })
        return `/rbc_web/rest/patter/playVideo?filePath=${data}`
      } catch (err) {
        return ''
      }
    },
    playAudio(val, index) {
      const {isSpeak, audioUrl} = val
      const domRef = this.$refs[`ssml${index}`][0]?.$el
      const audioElement = domRef?.querySelector('#ssml-audio')
      if (!audioElement.paused) {
        audioElement.pause()
        return
      }
      if (audioUrl && !isSpeak) {
        this.$nextTick(() => audioElement.play())
        return
      }
      this.getAudioPath(index).then(audioUrl => {
        if (!audioUrl) {
          this.$message.error('播放失败')
          return
        }
        this.$refs[`ssml${index}`][0].audioUrl = audioUrl
        this.$nextTick(() => audioElement.play())
      })
    }
  }
}
</script>
```

可单独使用，也和独立使用，以上为多个编辑器同时使用。使用时注意确保 ref 唯一。

### props

- text 文本内容。多行请用`\n`分割。
- richText htmlString 用于编辑时回显。

### event

- input 失去焦点时获取当前编辑框输入的内容。
- playAudio 将文字转为语音 试听。
- ssmlGenerated。生成 ssml 代码。

```html


  新
  <phoneme alphabet="py" ph="xian1">
    鲜
  </phoneme>
  的微风
  <say-as interpret-as="cardinal">
    666
  </say-as>
  吹动我的
  <say-as interpret-as="name"> 衣袂 </say-as>，
  <say-as interpret-as="address">
    如同
  </say-as>
  爱人的鼻息吹着我
  <phoneme alphabet="py" ph="di2">
    的
  </phoneme>
  手一样
  <say-as interpret-as="punctuation">
    。
  </say-as>
  <say-as interpret-as="date">
    2024-07-25
  </say-as>
  <say-as interpret-as="time">
    12:00:00
  </say-as>


```

### 注意事项

1. html 标签入库时要去除外层无用的标签

```javascript
extractInnermostContent(htmlString) {
      const tempDiv = document.createElement('div');
      tempDiv.innerHTML = htmlString;
      function getInnermostDiv(element) {
        let currentElement = element;
        while (currentElement.children.length === 1 && currentElement.firstElementChild.tagName.toLowerCase() === 'div') {
          currentElement = currentElement.firstElementChild;
        }
        return currentElement;
      }
      const outerDiv = tempDiv.firstElementChild;
      if (outerDiv && outerDiv.tagName.toLowerCase() === 'div') {
        const innermostDiv = getInnermostDiv(outerDiv);
        if (innermostDiv !== outerDiv) {
          outerDiv.innerHTML = innermostDiv.innerHTML;
        }
        return outerDiv.outerHTML;
      }
      return htmlString;
    }
```

2. html 转字符转

```javascript
htmlSerializer(targetElement) {
    if (!targetElement) return;
      const contentChildren = Array.from(targetElement?.childNodes);
      const clone = document.createElement('div');
      contentChildren.forEach(child => {
        clone.appendChild(child.cloneNode(true));
      });
      const serializer = new XMLSerializer();
      return serializer.serializeToString(clone);
    },
```

3. html 字符转反序列化

```javascript
 parserDomHtml(htmlString) {
      const parser = new DOMParser();
      const doc = parser.parseFromString(htmlString, 'text/html');
      return doc.body.firstChild;
    },
```

## License

[MIT](./LICENSE)

## 反馈

如果您在使用过程中有任何问题或发现了使用版本中存在的问题，请您与我联系。

## 联系方式

[联系我](mailto:lucifer199531@gmail.com)