|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2014 - 2019 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the Microsoft translation engine. |
|
8 """ |
|
9 |
|
10 from __future__ import unicode_literals |
|
11 try: |
|
12 str = unicode |
|
13 except NameError: |
|
14 pass |
|
15 |
|
16 from PyQt5.QtCore import QUrl, QDateTime, QByteArray, QTimer |
|
17 |
|
18 from .TranslationEngine import TranslationEngine |
|
19 |
|
20 |
|
21 class MicrosoftEngine(TranslationEngine): |
|
22 """ |
|
23 Class implementing the translation engine for the Microsoft |
|
24 translation service. |
|
25 """ |
|
26 AccessTokenUrl = \ |
|
27 "https://api.cognitive.microsoft.com/sts/v1.0/issueToken" |
|
28 TranslatorUrl = "https://api.microsofttranslator.com/V2/Http.svc/Translate" |
|
29 TextToSpeechUrl = "https://api.microsofttranslator.com/V2/Http.svc/Speak" |
|
30 |
|
31 def __init__(self, plugin, parent=None): |
|
32 """ |
|
33 Constructor |
|
34 |
|
35 @param plugin reference to the plugin object (TranslatorPlugin) |
|
36 @param parent reference to the parent object (QObject) |
|
37 """ |
|
38 super(MicrosoftEngine, self).__init__(plugin, parent) |
|
39 |
|
40 self.__mappings = { |
|
41 "zh-CN": "zh-CHS", |
|
42 "zh-TW": "zh-CHT", |
|
43 } |
|
44 |
|
45 QTimer.singleShot(0, self.availableTranslationsLoaded.emit) |
|
46 |
|
47 def engineName(self): |
|
48 """ |
|
49 Public method to return the name of the engine. |
|
50 |
|
51 @return engine name (string) |
|
52 """ |
|
53 return "microsoft" |
|
54 |
|
55 def supportedLanguages(self): |
|
56 """ |
|
57 Public method to get the supported languages. |
|
58 |
|
59 @return list of supported language codes (list of string) |
|
60 """ |
|
61 return ["ar", "bg", "ca", "cs", "da", "de", "en", |
|
62 "es", "et", "fi", "fr", "hi", "hu", "id", |
|
63 "it", "ja", "ko", "lt", "lv", "mt", |
|
64 "nl", "no", "pl", "pt", "ro", "ru", "sk", "sl", |
|
65 "sv", "th", "tr", "uk", "vi", "zh-CN", "zh-TW", |
|
66 ] |
|
67 |
|
68 def hasTTS(self): |
|
69 """ |
|
70 Public method indicating the Text-to-Speech capability. |
|
71 |
|
72 @return flag indicating the Text-to-Speech capability (boolean) |
|
73 """ |
|
74 return True |
|
75 |
|
76 def __mapLanguageCode(self, code): |
|
77 """ |
|
78 Private method to map a language code to the Microsoft code. |
|
79 |
|
80 @param code language code (string) |
|
81 @return mapped language code (string) |
|
82 """ |
|
83 if code in self.__mappings: |
|
84 return self.__mapping[code] |
|
85 else: |
|
86 return code |
|
87 |
|
88 def __getClientDataAzure(self): |
|
89 """ |
|
90 Private method to retrieve the client data. |
|
91 |
|
92 @return tuple giving the API subscription key and a flag indicating |
|
93 validity |
|
94 @rtype tuple of (str, bool) |
|
95 """ |
|
96 subscriptionKey = self.plugin.getPreferences("MsTranslatorKey") |
|
97 valid = bool(subscriptionKey) |
|
98 return subscriptionKey, valid |
|
99 |
|
100 def __getAccessToken(self, requestObject): |
|
101 """ |
|
102 Private slot to get an access token. |
|
103 |
|
104 If the stored token is no longer valid, get a new one and store it. |
|
105 |
|
106 @param requestObject reference to the request object |
|
107 (TranslatorRequest) |
|
108 @return access token (string) |
|
109 """ |
|
110 if self.plugin.getPreferences("MsAuthTokenExpire") > \ |
|
111 QDateTime.currentDateTime(): |
|
112 return self.plugin.getPreferences("MsAuthToken") |
|
113 |
|
114 # Token expired, get a new one |
|
115 subscriptionKey, valid = self.__getClientDataAzure() |
|
116 if not valid: |
|
117 return "" |
|
118 |
|
119 subscriptionHeader = (b"Ocp-Apim-Subscription-Key", |
|
120 subscriptionKey.encode("utf-8")) |
|
121 response, ok = requestObject.post( |
|
122 QUrl(self.AccessTokenUrl), QByteArray(b""), |
|
123 extraHeaders=[subscriptionHeader]) |
|
124 if ok: |
|
125 self.plugin.setPreferences("MsAuthToken", response) |
|
126 self.plugin.setPreferences( |
|
127 "MsAuthTokenExpire", |
|
128 QDateTime.currentDateTime().addSecs(8 * 60)) |
|
129 return response |
|
130 else: |
|
131 return "" |
|
132 |
|
133 def getTranslation(self, requestObject, text, originalLanguage, |
|
134 translationLanguage): |
|
135 """ |
|
136 Public method to translate the given text. |
|
137 |
|
138 @param requestObject reference to the request object |
|
139 (TranslatorRequest) |
|
140 @param text text to be translated (string) |
|
141 @param originalLanguage language code of the original (string) |
|
142 @param translationLanguage language code of the translation (string) |
|
143 @return tuple of translated text (string) and flag indicating |
|
144 success (boolean) |
|
145 """ |
|
146 subscriptionKey, valid = self.__getClientDataAzure() |
|
147 if not valid: |
|
148 return (self.tr("""You have not registered for the Microsoft""" |
|
149 """ Translation service."""), |
|
150 False) |
|
151 |
|
152 accessToken = self.__getAccessToken(requestObject) |
|
153 if not accessToken: |
|
154 return (self.tr("No valid access token available."), False) |
|
155 |
|
156 authHeader = (b"Authorization", |
|
157 "Bearer {0}".format(accessToken).encode("utf-8")) |
|
158 params = "?appid=&from={0}&to={1}&text={2}".format( |
|
159 self.__mapLanguageCode(originalLanguage), |
|
160 self.__mapLanguageCode(translationLanguage), |
|
161 text) |
|
162 url = QUrl(self.TranslatorUrl + params) |
|
163 response, ok = requestObject.get(url, extraHeaders=[authHeader]) |
|
164 if ok: |
|
165 response = str(response, "utf-8", "replace").strip() |
|
166 if response.startswith("<string") and \ |
|
167 response.endswith("</string>"): |
|
168 result = response.split(">", 1)[1].rsplit("<", 1)[0] |
|
169 else: |
|
170 result = self.tr("No translation available.") |
|
171 ok = False |
|
172 return result, ok |
|
173 |
|
174 def getTextToSpeechData(self, requestObject, text, language): |
|
175 """ |
|
176 Public method to pronounce the given text. |
|
177 |
|
178 @param requestObject reference to the request object |
|
179 (TranslatorRequest) |
|
180 @param text text to be pronounced (string) |
|
181 @param language language code of the text (string) |
|
182 @return tuple with pronounce data (QByteArray) or error string (string) |
|
183 and success flag (boolean) |
|
184 """ |
|
185 subscriptionKey, valid = self.__getClientDataAzure() |
|
186 if not valid: |
|
187 return (self.tr("""You have not registered for the Microsoft""" |
|
188 """ Translation service."""), |
|
189 False) |
|
190 |
|
191 accessToken = self.__getAccessToken(requestObject) |
|
192 if not accessToken: |
|
193 return (self.tr("No valid access token available."), False) |
|
194 |
|
195 params = "?language={0}&format={1}&options={2}&text={3}".format( |
|
196 self.__mapLanguageCode(language), |
|
197 "audio/wav", |
|
198 "MaxQuality", |
|
199 text) |
|
200 authHeader = (b"Authorization", |
|
201 "Bearer {0}".format(accessToken).encode("utf-8")) |
|
202 url = QUrl(self.TextToSpeechUrl + params) |
|
203 data, ok = requestObject.get(url, extraHeaders=[authHeader]) |
|
204 if not ok: |
|
205 data = self.tr("No Text-to-Speech for the selected language" |
|
206 " available.") |
|
207 return data, ok |