SsmlGenerator::getSsml()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 1
b 0
f 0
cc 1
nc 1
nop 0
crap 1
1
<?php
2
3
namespace MaxBeckers\AmazonAlexa\Helper;
4
5
use MaxBeckers\AmazonAlexa\Exception\InvalidSsmlException;
6
7
/**
8
 * @author Maximilian Beckers <[email protected]>
9
 */
10
class SsmlGenerator implements SsmlTypes
11
{
12
    /**
13
     * Enable this flag, when you need escaped special chars in your content (for example escaped "&").
14
     *
15
     * @var bool
16
     */
17
    public $escapeSpecialChars = false;
18
19
    /**
20
     * @var string[]
21
     */
22
    private $parts = [];
23
24
    /**
25
     * Clear the current ssml parts.
26
     */
27 1
    public function clear()
28
    {
29 1
        $this->parts = [];
30
    }
31
32
    /**
33
     * @return string
34
     */
35 46
    public function getSsml()
36
    {
37 46
        return sprintf('<speak>%s</speak>', implode(' ', $this->parts));
38
    }
39
40
    /**
41
     * Say a default text.
42
     *
43
     * @param string $text
44
     */
45 5
    public function say(string $text)
46
    {
47 5
        $this->parts[] = $this->textEscapeSpecialChars($text);
48
    }
49
50
    /**
51
     * Play audio in output.
52
     * For more specifications of the mp3 file @see https://developer.amazon.com/de/docs/custom-skills/speech-synthesis-markup-language-ssml-reference.html#audio.
53
     *
54
     * @param string $mp3Url
55
     *
56
     * @throws InvalidSsmlException
57
     */
58 3
    public function playMp3(string $mp3Url)
59
    {
60 3
        if (1 !== preg_match('/^(https:\/\/.*\.mp3.*)$/i', $mp3Url) && 0 !== strpos($mp3Url, 'soundbank://')) {
61 1
            throw new InvalidSsmlException(sprintf('"%s" in not a valid mp3 url!', $mp3Url));
62
        }
63 2
        $this->parts[] = sprintf('<audio src="%s" />', $mp3Url);
64
    }
65
66
    /**
67
     * Make a pause (or remove with none/x-weak).
68
     * Possible values @see https://developer.amazon.com/de/docs/custom-skills/speech-synthesis-markup-language-ssml-reference.html#break.
69
     *
70
     * @param string $strength
71
     *
72
     * @throws InvalidSsmlException
73
     */
74 2
    public function pauseStrength(string $strength)
75
    {
76 2
        if (!in_array($strength, self::BREAK_STRENGTHS, true)) {
77 1
            throw new InvalidSsmlException(sprintf('Break strength must be one of "%s"!', implode(',', self::BREAK_STRENGTHS)));
78
        }
79 1
        $this->parts[] = sprintf('<break strength="%s" />', $strength);
80
    }
81
82
    /**
83
     * Make a pause with duration time as string in seconds(s) or milliseconds(ms).
84
     * For example '10s' or '10000ms' to break 10 seconds.
85
     *
86
     * @param string $time
87
     *
88
     * @throws InvalidSsmlException
89
     */
90 2
    public function pauseTime(string $time)
91
    {
92 2
        if (1 !== preg_match('/^(\d+(s|ms))$/i', $time)) {
93 1
            throw new InvalidSsmlException('Time must be seconds or milliseconds!');
94
        }
95 1
        $this->parts[] = sprintf('<break time="%s" />', $time);
96
    }
97
98
    /**
99
     * Say a text with effect.
100
     *
101
     * @param string $text
102
     * @param string $effect
103
     *
104
     * @throws InvalidSsmlException
105
     */
106 3
    public function sayWithAmazonEffect(string $text, string $effect = self::AMAZON_EFFECT_WHISPERED)
107
    {
108 3
        if (!in_array($effect, self::AMAZON_EFFECTS, true)) {
109 1
            throw new InvalidSsmlException(sprintf('Amazon:effect name must be one of "%s"!', implode(',', self::AMAZON_EFFECTS)));
110
        }
111 2
        $this->parts[] = sprintf('<amazon:effect name="%s">%s</amazon:effect>', $effect, $this->textEscapeSpecialChars($text));
112
    }
113
114
    /**
115
     * Whisper a text.
116
     *
117
     * @param string $text
118
     */
119 1
    public function whisper(string $text)
120
    {
121 1
        $this->sayWithAmazonEffect($text, self::AMAZON_EFFECT_WHISPERED);
122
    }
123
124
    /**
125
     * Say with emphasis.
126
     *
127
     * @param string $text
128
     * @param string $level
129
     *
130
     * @throws InvalidSsmlException
131
     */
132 2
    public function emphasis(string $text, string $level)
133
    {
134 2
        if (!in_array($level, self::EMPHASIS_LEVELS, true)) {
135 1
            throw new InvalidSsmlException(sprintf('Emphasis level must be one of "%s"!', implode(',', self::EMPHASIS_LEVELS)));
136
        }
137 1
        $this->parts[] = sprintf('<emphasis level="%s">%s</emphasis>', $level, $this->textEscapeSpecialChars($text));
138
    }
139
140
    /**
141
     * Say a text pronounced in the given language.
142
     *
143
     * @param string $language
144
     * @param string $text
145
     *
146
     * @throws InvalidSsmlException
147
     */
148 2
    public function pronounceInLanguage(string $language, string $text)
149
    {
150 2
        if (!in_array($language, self::LANGUAGE_LIST, true)) {
151 1
            throw new InvalidSsmlException(sprintf('Language must be one of "%s"!', implode(',', self::LANGUAGE_LIST)));
152
        }
153 1
        $this->parts[] = sprintf('<lang xml:lang="%s">%s</lang>', $language, $this->textEscapeSpecialChars($text));
154
    }
155
156
    /**
157
     * Say a paragraph.
158
     *
159
     * @param string $paragraph
160
     */
161 1
    public function paragraph(string $paragraph)
162
    {
163 1
        $this->parts[] = sprintf('<p>%s</p>', $this->textEscapeSpecialChars($paragraph));
164
    }
165
166
    /**
167
     * Say a text with a phoneme.
168
     *
169
     * @param string $alphabet
170
     * @param string $ph
171
     * @param string $text
172
     *
173
     * @throws InvalidSsmlException
174
     */
175 3
    public function phoneme(string $alphabet, string $ph, string $text)
176
    {
177 3
        if (!in_array($alphabet, self::PHONEME_ALPHABETS, true)) {
178 1
            throw new InvalidSsmlException(sprintf('Phoneme alphabet must be one of "%s"!', implode(',', self::PHONEME_ALPHABETS)));
179
        }
180 2
        $this->parts[] = sprintf('<phoneme alphabet="%s" ph="%s">%s</phoneme>', $alphabet, $ph, $this->textEscapeSpecialChars($text));
181
    }
182
183
    /**
184
     * Say a text with a prosody.
185
     *
186
     * There are three different modes of prosody: volume, pitch, and rate.
187
     * For more details @see https://developer.amazon.com/de/docs/custom-skills/speech-synthesis-markup-language-ssml-reference.html#prosody
188
     *
189
     * @param string $mode
190
     * @param string $value
191
     * @param string $text
192
     *
193
     * @throws InvalidSsmlException
194
     */
195 2
    public function prosody(string $mode, string $value, string $text)
196
    {
197 2
        if (!isset(self::PROSODIES[$mode])) {
198 1
            throw new InvalidSsmlException(sprintf('Prosody mode must be one of "%s"!', implode(',', array_keys(self::PROSODIES))));
199
        }
200
        // todo validate value for mode
201 1
        $this->parts[] = sprintf('<prosody %s="%s">%s</prosody>', $mode, $value, $this->textEscapeSpecialChars($text));
202
    }
203
204
    /**
205
     * Say a sentence.
206
     *
207
     * @param string $text
208
     */
209 1
    public function sentence(string $text)
210
    {
211 1
        $this->parts[] = sprintf('<s>%s</s>', $this->textEscapeSpecialChars($text));
212
    }
213
214
    /**
215
     * Say a text with interpretation.
216
     *
217
     * @param string $interpretAs
218
     * @param string $text
219
     * @param string $format
220
     *
221
     * @throws InvalidSsmlException
222
     */
223 3
    public function sayAs(string $interpretAs, string $text, string $format = '')
224
    {
225 3
        if (!in_array($interpretAs, self::SAY_AS_INTERPRET_AS, true)) {
226 1
            throw new InvalidSsmlException(sprintf('Interpret as attribute must be one of "%s"!', implode(',', self::SAY_AS_INTERPRET_AS)));
227
        }
228 2
        if ($format) {
229 1
            $this->parts[] = sprintf('<say-as interpret-as="%s" format="%s">%s</say-as>', $interpretAs, $format, $this->textEscapeSpecialChars($text));
230
        } else {
231 1
            $this->parts[] = sprintf('<say-as interpret-as="%s">%s</say-as>', $interpretAs, $this->textEscapeSpecialChars($text));
232
        }
233
    }
234
235
    /**
236
     * Say an alias.
237
     * For example replace the abbreviated chemical elements with the full words.
238
     *
239
     * @param string $alias
240
     * @param string $text
241
     */
242 1
    public function alias(string $alias, string $text)
243
    {
244 1
        $this->parts[] = sprintf('<sub alias="%s">%s</sub>', $alias, $this->textEscapeSpecialChars($text));
245
    }
246
247
    /**
248
     * Say a text with the voice of the given person.
249
     *
250
     * @param string $voice
251
     * @param string $text
252
     *
253
     * @throws InvalidSsmlException
254
     */
255 27
    public function sayWithVoice(string $voice, string $text)
256
    {
257 27
        if (!in_array($voice, self::VOICES, true)) {
258 1
            throw new InvalidSsmlException(sprintf('Voice must be one of "%s"!', implode(',', self::VOICES)));
259
        }
260 26
        $this->parts[] = sprintf('<voice name="%s">%s</voice>', $voice, $this->textEscapeSpecialChars($text));
261
    }
262
263
    /**
264
     * Say a word with defined word's parts to speach.
265
     *
266
     * @param string $role
267
     * @param string $text
268
     *
269
     * @throws InvalidSsmlException
270
     */
271 2
    public function word(string $role, string $text)
272
    {
273 2
        if (!in_array($role, self::INTERPRET_WORDS, true)) {
274 1
            throw new InvalidSsmlException(sprintf('Interpret as attribute must be one of "%s"!', implode(',', self::INTERPRET_WORDS)));
275
        }
276 1
        $this->parts[] = sprintf('<w role="%s">%s</w>', $role, $this->textEscapeSpecialChars($text));
277
    }
278
279
    /**
280
     * Escape special chars for ssml output (for example "&").
281
     *
282
     * @param string $text
283
     *
284
     * @return string
285
     */
286 44
    private function textEscapeSpecialChars(string $text): string
287
    {
288 44
        if ($this->escapeSpecialChars) {
289 1
            $text = htmlspecialchars($text);
290
        }
291
292 44
        return $text;
293
    }
294
}
295