Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
LarynxEngine
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
2 / 2
7
100.00% covered (success)
100.00%
1 / 1
 register
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getAudioData
n/a
0 / 0
n/a
0 / 0
4
 getSsml
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3namespace MediaWiki\Extension\Phonos\Engine;
4
5use DOMDocument;
6use MediaWiki\Extension\Phonos\Exception\PhonosException;
7
8class LarynxEngine extends Engine {
9
10    protected string $apiEndpoint;
11
12    protected function register(): void {
13        $this->apiEndpoint = $this->config->get( 'PhonosApiEndpointLarynx' );
14    }
15
16    /**
17     * @inheritDoc
18     * @codeCoverageIgnore
19     * @throws PhonosException
20     */
21    public function getAudioData( AudioParams $params ): string {
22        $persistedAudio = $this->getPersistedAudio( $params );
23        if ( $persistedAudio ) {
24            return $persistedAudio;
25        }
26
27        $ssml = trim( $this->getSsml( $params ) );
28        $url = $this->apiEndpoint . '?' . http_build_query( [
29            'ssml' => true,
30            // TODO: should the voice be configurable, too?
31            'voice' => 'en-us/blizzard_lessac-glow_tts',
32            'text' => $ssml,
33        ] );
34        $options = [
35            'method' => 'GET'
36        ];
37
38        if ( $this->apiProxy ) {
39            $options['proxy'] = $this->apiProxy;
40        }
41
42        $request = $this->requestFactory->create(
43            $url,
44            $options,
45            __METHOD__
46        );
47        $status = $request->execute();
48
49        if ( !$status->isOK() ) {
50            $error = $status->getMessage()->text();
51            throw new PhonosException( 'phonos-engine-error', [ 'Larynx', $error ] );
52        }
53
54        $out = $this->convertWavToMp3( $request->getContent() );
55        $this->persistAudio( $params, $out );
56
57        return $out;
58    }
59
60    /**
61     * @inheritDoc
62     */
63    public function getSsml( AudioParams $params ): string {
64        $ipa = trim( $params->getIpa() );
65        $text = $params->getText() ?: $ipa;
66
67        $ssmlDoc = new DOMDocument( '1.0' );
68
69        $speakNode = $ssmlDoc->createElement( 'speak' );
70        $speakNode->setAttribute( 'xmlns', 'http://www.w3.org/2001/10/synthesis' );
71        $speakNode->setAttribute( 'version', '1.1' );
72        $speakNode->setAttribute( 'xml:lang', $params->getLang() );
73        $ssmlDoc->appendChild( $speakNode );
74
75        /**
76         * Adds the following to the <speak> node:
77         *   <phoneme alphabet="ipa" ph={$ipa}>
78         *    <w>{$text} ?: {$ipa}</w>
79         *   </phoneme>
80         */
81
82        $phonemeNode = $ssmlDoc->createElement( 'phoneme' );
83        $phonemeNode->setAttribute( 'alphabet', 'ipa' );
84        $phonemeNode->setAttribute( 'ph', $ipa );
85        $wNode = $ssmlDoc->createElement( 'w', $text );
86        $phonemeNode->appendChild( $wNode );
87        $speakNode->appendChild( $phonemeNode );
88
89        return $ssmlDoc->saveXML();
90    }
91}