SsmlGenerator::phoneme()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

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