ssml_renderer.js 2.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. import { Engine } from '../common/engine.js';
  2. import * as EngineConst from '../common/engine_const.js';
  3. import { XmlRenderer } from './xml_renderer.js';
  4. export class SsmlRenderer extends XmlRenderer {
  5. finalize(str) {
  6. return ('<?xml version="1.0"?><speak version="1.1"' +
  7. ' xmlns="http://www.w3.org/2001/10/synthesis"' +
  8. ` xml:lang="${Engine.getInstance().locale}">` +
  9. '<prosody rate="' +
  10. Engine.getInstance().getRate() +
  11. '%">' +
  12. this.separator +
  13. str +
  14. this.separator +
  15. '</prosody></speak>');
  16. }
  17. pause(pause) {
  18. return ('<break ' +
  19. 'time="' +
  20. this.pauseValue(pause[EngineConst.personalityProps.PAUSE]) +
  21. 'ms"/>');
  22. }
  23. prosodyElement(attr, value) {
  24. value = Math.floor(this.applyScaleFunction(value));
  25. const valueStr = value < 0 ? value.toString() : '+' + value.toString();
  26. return ('<prosody ' +
  27. attr.toLowerCase() +
  28. '="' +
  29. valueStr +
  30. (attr === EngineConst.personalityProps.VOLUME ? '>' : '%">'));
  31. }
  32. closeTag(_tag) {
  33. return '</prosody>';
  34. }
  35. markup(descrs) {
  36. SsmlRenderer.MARKS = {};
  37. return super.markup(descrs);
  38. }
  39. merge(spans) {
  40. const result = [];
  41. let lastMark = '';
  42. for (let i = 0; i < spans.length; i++) {
  43. const span = spans[i];
  44. if (this.isEmptySpan(span))
  45. continue;
  46. const kind = SsmlRenderer.MARK_KIND ? span.attributes['kind'] : '';
  47. const id = Engine.getInstance().automark
  48. ? span.attributes['id']
  49. : Engine.getInstance().mark
  50. ? span.attributes['extid']
  51. : '';
  52. if (id &&
  53. id !== lastMark &&
  54. !(SsmlRenderer.MARK_ONCE && SsmlRenderer.MARKS[id])) {
  55. result.push(kind ? `<mark name="${id}" kind="${kind}"/>` : `<mark name="${id}"/>`);
  56. lastMark = id;
  57. SsmlRenderer.MARKS[id] = true;
  58. }
  59. if (Engine.getInstance().character &&
  60. span.speech.length === 1 &&
  61. span.speech.match(/[a-zA-Z]/)) {
  62. result.push('<say-as interpret-as="' +
  63. SsmlRenderer.CHARACTER_ATTR +
  64. '">' +
  65. span.speech +
  66. '</say-as>');
  67. }
  68. else {
  69. result.push(span.speech);
  70. }
  71. }
  72. return result.join(this.separator);
  73. }
  74. isEmptySpan(span) {
  75. const sep = span.attributes['separator'];
  76. return span.speech.match(/^\s*$/) && (!sep || sep.match(/^\s*$/));
  77. }
  78. }
  79. SsmlRenderer.MARK_ONCE = false;
  80. SsmlRenderer.MARK_KIND = true;
  81. SsmlRenderer.CHARACTER_ATTR = 'character';
  82. SsmlRenderer.MARKS = {};