File indexing completed on 2025-04-27 03:58:39
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2018-02-22 0007 * Description : A text translator using web-services - TTS support. 0008 * 0009 * SPDX-FileCopyrightText: 2018-2022 by Hennadii Chernyshchyk <genaloner at gmail dot com> 0010 * SPDX-FileCopyrightText: 2021-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0011 * 0012 * SPDX-License-Identifier: GPL-2.0-or-later 0013 * 0014 * ============================================================ */ 0015 0016 #include "donlinetranslator_p.h" 0017 0018 namespace Digikam 0019 { 0020 0021 DOnlineTts::DOnlineTts(QObject* const parent) 0022 : QObject(parent), 0023 d(new Private) 0024 { 0025 } 0026 0027 DOnlineTts::~DOnlineTts() 0028 { 0029 delete d; 0030 } 0031 0032 void DOnlineTts::generateUrls(const QString& text, 0033 DOnlineTranslator::Engine engine, 0034 DOnlineTranslator::Language lang, 0035 Voice voice, 0036 Emotion emotion) 0037 { 0038 // Get speech 0039 0040 QString unparsedText = text; 0041 0042 switch (engine) 0043 { 0044 case DOnlineTranslator::Google: 0045 { 0046 if (voice != NoVoice) 0047 { 0048 setError(UnsupportedVoice, i18nc("@info", "Selected engine %1 does not support voice settings", 0049 QString::fromUtf8(QMetaEnum::fromType<DOnlineTranslator::Engine>().valueToKey(engine)))); 0050 return; 0051 } 0052 0053 if (emotion != NoEmotion) 0054 { 0055 setError(UnsupportedEmotion, i18nc("@info", "Selected engine %1 does not support emotion settings", 0056 QString::fromUtf8(QMetaEnum::fromType<DOnlineTranslator::Engine>().valueToKey(engine)))); 0057 return; 0058 } 0059 0060 const QString langString = languageApiCode(engine, lang); 0061 0062 if (langString.isNull()) 0063 return; 0064 0065 // Google has a limit of characters per tts request. If the query is larger, then it should be splited into several 0066 0067 while (!unparsedText.isEmpty()) 0068 { 0069 const int splitIndex = DOnlineTranslator::getSplitIndex(unparsedText, d->s_googleTtsLimit); // Split the part by special symbol 0070 0071 // Generate URL API for add it to the playlist 0072 0073 QUrl apiUrl(QStringLiteral("https://translate.googleapis.com/translate_tts")); 0074 const QString query = QStringLiteral("ie=UTF-8&client=gtx&tl=%1&q=%2") 0075 .arg(langString, QString::fromUtf8(QUrl::toPercentEncoding(unparsedText.left(splitIndex)))); 0076 0077 #if defined(Q_OS_LINUX) 0078 0079 apiUrl.setQuery(query); 0080 0081 #elif defined(Q_OS_WIN) 0082 0083 apiUrl.setQuery(query, QUrl::DecodedMode); 0084 0085 #endif 0086 d->media.append(apiUrl); 0087 0088 // Remove the said part from the next saying 0089 0090 unparsedText = unparsedText.mid(splitIndex); 0091 } 0092 0093 break; 0094 } 0095 0096 case DOnlineTranslator::Yandex: 0097 { 0098 const QString langString = languageApiCode(engine, lang); 0099 0100 if (langString.isNull()) 0101 return; 0102 0103 const QString voiceString = voiceApiCode(engine, voice); 0104 0105 if (voiceString.isNull()) 0106 return; 0107 0108 const QString emotionString = emotionApiCode(engine, emotion); 0109 0110 if (emotionString.isNull()) 0111 return; 0112 0113 // Yandex has a limit of characters per tts request. If the query is larger, then it should be splited into several 0114 0115 while (!unparsedText.isEmpty()) 0116 { 0117 const int splitIndex = DOnlineTranslator::getSplitIndex(unparsedText, d->s_yandexTtsLimit); // Split the part by special symbol 0118 0119 // Generate URL API for add it to the playlist 0120 QUrl apiUrl(QStringLiteral("https://tts.voicetech.yandex.net/tts")); 0121 const QString query = QStringLiteral("text=%1&lang=%2&speaker=%3&emotion=%4&format=mp3") 0122 .arg(QString::fromUtf8(QUrl::toPercentEncoding(unparsedText.left(splitIndex))), langString, voiceString, emotionString); 0123 0124 #if defined(Q_OS_LINUX) 0125 0126 apiUrl.setQuery(query); 0127 0128 #elif defined(Q_OS_WIN) 0129 0130 apiUrl.setQuery(query, QUrl::DecodedMode); 0131 0132 #endif 0133 0134 d->media.append(apiUrl); 0135 0136 // Remove the said part from the next saying 0137 0138 unparsedText = unparsedText.mid(splitIndex); 0139 } 0140 0141 break; 0142 } 0143 0144 case DOnlineTranslator::Bing: 0145 case DOnlineTranslator::LibreTranslate: 0146 case DOnlineTranslator::Lingva: 0147 { 0148 // NOTE: 0149 // Lingva returns audio in strange format, use placeholder, until we'll figure it out 0150 // 0151 // Example: https://lingva.ml/api/v1/audio/en/Hello%20World! 0152 // Will return json with uint bytes array, according to documentation 0153 // See: https://github.com/TheDavidDelta/lingva-translate#public-apis 0154 setError(UnsupportedEngine, i18nc("@info", "%1 engine does not support TTS", 0155 QString::fromUtf8(QMetaEnum::fromType<DOnlineTranslator::Engine>().valueToKey(engine)))); 0156 break; 0157 } 0158 } 0159 } 0160 0161 QList<QUrl> DOnlineTts::media() const 0162 { 0163 return d->media; 0164 } 0165 0166 QString DOnlineTts::errorString() const 0167 { 0168 return d->errorString; 0169 } 0170 0171 DOnlineTts::TtsError DOnlineTts::error() const 0172 { 0173 return d->error; 0174 } 0175 0176 QString DOnlineTts::voiceCode(Voice voice) 0177 { 0178 return Private::s_voiceCodes.value(voice); 0179 } 0180 0181 QString DOnlineTts::emotionCode(Emotion emotion) 0182 { 0183 return Private::s_emotionCodes.value(emotion); 0184 } 0185 0186 DOnlineTts::Emotion DOnlineTts::emotion(const QString& emotionCode) 0187 { 0188 return Private::s_emotionCodes.key(emotionCode, NoEmotion); 0189 } 0190 0191 DOnlineTts::Voice DOnlineTts::voice(const QString& voiceCode) 0192 { 0193 return Private::s_voiceCodes.key(voiceCode, NoVoice); 0194 } 0195 0196 void DOnlineTts::setError(TtsError error, const QString& errorString) 0197 { 0198 d->error = error; 0199 d->errorString = errorString; 0200 } 0201 0202 // Returns engine-specific language code for tts 0203 QString DOnlineTts::languageApiCode(DOnlineTranslator::Engine engine, DOnlineTranslator::Language lang) 0204 { 0205 switch (engine) 0206 { 0207 case DOnlineTranslator::Google: 0208 case DOnlineTranslator::Lingva: // Lingva is a frontend to Google Translate 0209 { 0210 if (lang != DOnlineTranslator::Auto) 0211 { 0212 return DOnlineTranslator::languageApiCode(engine, lang); // Google use the same codes for tts (except 'auto') 0213 } 0214 0215 break; 0216 } 0217 0218 case DOnlineTranslator::Yandex: 0219 { 0220 switch (lang) 0221 { 0222 case DOnlineTranslator::Russian: 0223 { 0224 return QStringLiteral("ru_RU"); 0225 } 0226 0227 case DOnlineTranslator::Tatar: 0228 { 0229 return QStringLiteral("tr_TR"); 0230 } 0231 0232 case DOnlineTranslator::English: 0233 { 0234 return QStringLiteral("en_GB"); 0235 } 0236 0237 default: 0238 { 0239 break; 0240 } 0241 } 0242 0243 break; 0244 } 0245 0246 default: 0247 { 0248 break; 0249 } 0250 } 0251 0252 setError(UnsupportedLanguage, i18nc("@info", "Selected language %1 is not supported for %2", 0253 QString::fromUtf8(QMetaEnum::fromType<DOnlineTranslator::Language>().valueToKey(lang)), 0254 QString::fromUtf8(QMetaEnum::fromType<DOnlineTranslator::Engine>().valueToKey(engine)))); 0255 0256 return QString(); 0257 } 0258 0259 QString DOnlineTts::voiceApiCode(DOnlineTranslator::Engine engine, Voice voice) 0260 { 0261 if (engine == DOnlineTranslator::Yandex) 0262 { 0263 if (voice == NoVoice) 0264 { 0265 return voiceCode(Zahar); 0266 } 0267 0268 return voiceCode(voice); 0269 } 0270 0271 setError(UnsupportedVoice, i18nc("@info", "Selected voice %1 is not supported for %2", 0272 QString::fromUtf8(QMetaEnum::fromType<Voice>().valueToKey(voice)), 0273 QString::fromUtf8(QMetaEnum::fromType<DOnlineTranslator::Engine>().valueToKey(engine)))); 0274 0275 return QString(); 0276 } 0277 0278 QString DOnlineTts::emotionApiCode(DOnlineTranslator::Engine engine, Emotion emotion) 0279 { 0280 if (engine == DOnlineTranslator::Yandex) 0281 { 0282 if (emotion == NoEmotion) 0283 { 0284 return emotionCode(Neutral); 0285 } 0286 0287 return emotionCode(emotion); 0288 } 0289 0290 setError(UnsupportedEmotion, i18nc("@info", "Selected emotion %1 is not supported for %2", 0291 QString::fromUtf8(QMetaEnum::fromType<Emotion>().valueToKey(emotion)), 0292 QString::fromUtf8(QMetaEnum::fromType<DOnlineTranslator::Engine>().valueToKey(engine)))); 0293 0294 return QString(); 0295 } 0296 0297 } // namespace Digikam