|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2014 - 2017 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 |
|
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 def engineName(self): |
|
46 """ |
|
47 Public method to return the name of the engine. |
|
48 |
|
49 @return engine name (string) |
|
50 """ |
|
51 return "microsoft" |
|
52 |
|
53 def supportedLanguages(self): |
|
54 """ |
|
55 Public method to get the supported languages. |
|
56 |
|
57 @return list of supported language codes (list of string) |
|
58 """ |
|
59 return ["ar", "bg", "ca", "cs", "da", "de", "en", |
|
60 "es", "et", "fi", "fr", "hi", "hu", "id", |
|
61 "it", "ja", "ko", "lt", "lv", "mt", |
|
62 "nl", "no", "pl", "pt", "ro", "ru", "sk", "sl", |
|
63 "sv", "th", "tr", "uk", "vi", "zh-CN", "zh-TW", |
|
64 ] |
|
65 |
|
66 def hasTTS(self): |
|
67 """ |
|
68 Public method indicating the Text-to-Speech capability. |
|
69 |
|
70 @return flag indicating the Text-to-Speech capability (boolean) |
|
71 """ |
|
72 return True |
|
73 |
|
74 def __mapLanguageCode(self, code): |
|
75 """ |
|
76 Private method to map a language code to the Microsoft code. |
|
77 |
|
78 @param code language code (string) |
|
79 @return mapped language code (string) |
|
80 """ |
|
81 if code in self.__mappings: |
|
82 return self.__mapping[code] |
|
83 else: |
|
84 return code |
|
85 |
|
86 def __getClientDataAzure(self): |
|
87 """ |
|
88 Private method to retrieve the client data. |
|
89 |
|
90 @return tuple giving the API subscription key and a flag indicating |
|
91 validity |
|
92 @rtype tuple of (str, bool) |
|
93 """ |
|
94 subscriptionKey = self.plugin.getPreferences("MsTranslatorKey") |
|
95 valid = bool(subscriptionKey) |
|
96 return subscriptionKey, valid |
|
97 |
|
98 def __getAccessToken(self, requestObject): |
|
99 """ |
|
100 Private slot to get an access token. |
|
101 |
|
102 If the stored token is no longer valid, get a new one and store it. |
|
103 |
|
104 @param requestObject reference to the request object |
|
105 (TranslatorRequest) |
|
106 @return access token (string) |
|
107 """ |
|
108 if self.plugin.getPreferences("MsAuthTokenExpire") > \ |
|
109 QDateTime.currentDateTime(): |
|
110 return self.plugin.getPreferences("MsAuthToken") |
|
111 |
|
112 # Token expired, get a new one |
|
113 subscriptionKey, valid = self.__getClientDataAzure() |
|
114 if not valid: |
|
115 return "" |
|
116 |
|
117 subscriptionHeader = (b"Ocp-Apim-Subscription-Key", |
|
118 subscriptionKey.encode("utf-8")) |
|
119 response, ok = requestObject.post( |
|
120 QUrl(self.AccessTokenUrl), QByteArray(b""), |
|
121 extraHeaders=[subscriptionHeader]) |
|
122 if ok: |
|
123 self.plugin.setPreferences("MsAuthToken", response) |
|
124 self.plugin.setPreferences( |
|
125 "MsAuthTokenExpire", |
|
126 QDateTime.currentDateTime().addSecs(8 * 60)) |
|
127 return response |
|
128 else: |
|
129 return "" |
|
130 |
|
131 def getTranslation(self, requestObject, text, originalLanguage, |
|
132 translationLanguage): |
|
133 """ |
|
134 Public method to translate the given text. |
|
135 |
|
136 @param requestObject reference to the request object |
|
137 (TranslatorRequest) |
|
138 @param text text to be translated (string) |
|
139 @param originalLanguage language code of the original (string) |
|
140 @param translationLanguage language code of the translation (string) |
|
141 @return tuple of translated text (string) and flag indicating |
|
142 success (boolean) |
|
143 """ |
|
144 subscriptionKey, valid = self.__getClientDataAzure() |
|
145 if not valid: |
|
146 return (self.tr("""You have not registered for the Microsoft""" |
|
147 """ Translation service."""), |
|
148 False) |
|
149 |
|
150 accessToken = self.__getAccessToken(requestObject) |
|
151 if not accessToken: |
|
152 return (self.tr("No valid access token available."), False) |
|
153 |
|
154 authHeader = (b"Authorization", |
|
155 "Bearer {0}".format(accessToken).encode("utf-8")) |
|
156 params = "?appid=&from={0}&to={1}&text={2}".format( |
|
157 self.__mapLanguageCode(originalLanguage), |
|
158 self.__mapLanguageCode(translationLanguage), |
|
159 text) |
|
160 url = QUrl(self.TranslatorUrl + params) |
|
161 response, ok = requestObject.get(url, extraHeaders=[authHeader]) |
|
162 response = str(response, "utf-8", "replace") |
|
163 if ok and response.startswith("<string") and \ |
|
164 response.endswith("</string>"): |
|
165 result = response.split(">", 1)[1].rsplit("<", 1)[0] |
|
166 else: |
|
167 result = self.tr("No translation available.") |
|
168 ok = False |
|
169 return result, ok |
|
170 |
|
171 def getTextToSpeechData(self, requestObject, text, language): |
|
172 """ |
|
173 Public method to pronounce the given text. |
|
174 |
|
175 @param requestObject reference to the request object |
|
176 (TranslatorRequest) |
|
177 @param text text to be pronounced (string) |
|
178 @param language language code of the text (string) |
|
179 @return tuple with pronounce data (QByteArray) or error string (string) |
|
180 and success flag (boolean) |
|
181 """ |
|
182 subscriptionKey, valid = self.__getClientDataAzure() |
|
183 if not valid: |
|
184 return (self.tr("""You have not registered for the Microsoft""" |
|
185 """ Translation service."""), |
|
186 False) |
|
187 |
|
188 accessToken = self.__getAccessToken(requestObject) |
|
189 if not accessToken: |
|
190 return (self.tr("No valid access token available."), False) |
|
191 |
|
192 params = "?language={0}&format={1}&options={2}&text={3}".format( |
|
193 self.__mapLanguageCode(language), |
|
194 "audio/wav", |
|
195 "MaxQuality", |
|
196 text) |
|
197 authHeader = (b"Authorization", |
|
198 "Bearer {0}".format(accessToken).encode("utf-8")) |
|
199 url = QUrl(self.TextToSpeechUrl + params) |
|
200 data, ok = requestObject.get(url, extraHeaders=[authHeader]) |
|
201 if not ok: |
|
202 data = self.tr("No Text-to-Speech for the selected language" |
|
203 " available.") |
|
204 return data, ok |