From f0f786a55135bddf0494cf2c286dc774d16fdea2 Mon Sep 17 00:00:00 2001 From: cdhigh Date: Tue, 23 Apr 2024 21:38:01 -0300 Subject: [PATCH] optimize tts feature --- application/lib/ebook_tts/engines/__init__.py | 2 +- application/lib/ebook_tts/engines/azure.py | 1 + application/lib/ebook_tts/engines/google.py | 2 + application/lib/ebook_tts/engines/tts_base.py | 1 + application/lib/ebook_tts/html_audiolator.py | 6 +- application/lib/pymp3cat.py | 175 +++++----- application/static/base.js | 37 +-- application/templates/book_audiolator.html | 8 +- application/templates/book_translator.html | 2 +- application/translations/messages.pot | 279 +++++++++------- .../tr_TR/LC_MESSAGES/messages.mo | Bin 24994 -> 25709 bytes .../tr_TR/LC_MESSAGES/messages.po | 300 +++++++++++------- .../translations/zh/LC_MESSAGES/messages.mo | Bin 23569 -> 24167 bytes .../translations/zh/LC_MESSAGES/messages.po | 299 ++++++++++------- application/view/adv.py | 14 +- application/view/translator.py | 48 ++- application/work/worker.py | 57 ++-- 17 files changed, 744 insertions(+), 487 deletions(-) diff --git a/application/lib/ebook_tts/engines/__init__.py b/application/lib/ebook_tts/engines/__init__.py index 72147c8f..7861e6d7 100644 --- a/application/lib/ebook_tts/engines/__init__.py +++ b/application/lib/ebook_tts/engines/__init__.py @@ -4,7 +4,7 @@ from .azure import AzureTTS from .google import GoogleWebTTSFree, GoogleTextToSpeech builtin_tts_engines = { - AzureTTS.name: AzureTTS, GoogleWebTTSFree.name: GoogleWebTTSFree, GoogleTextToSpeech.name: GoogleTextToSpeech, + AzureTTS.name: AzureTTS, } diff --git a/application/lib/ebook_tts/engines/azure.py b/application/lib/ebook_tts/engines/azure.py index a7ec2ef3..c3613863 100644 --- a/application/lib/ebook_tts/engines/azure.py +++ b/application/lib/ebook_tts/engines/azure.py @@ -207,6 +207,7 @@ class AzureTTS(TTSBase): max_len_per_request = 1000 languages = azuretts_languages regions = azure_regions + engine_url = 'https://azure.microsoft.com/en-us/products/ai-services/text-to-speech' region_url = 'https://learn.microsoft.com/en-us/azure/ai-services/speech-service/regions' voice_url = 'https://learn.microsoft.com/en-us/azure/ai-services/speech-service/language-support?tabs=tts' language_url = 'https://learn.microsoft.com/en-us/azure/ai-services/speech-service/language-support?tabs=tts' diff --git a/application/lib/ebook_tts/engines/google.py b/application/lib/ebook_tts/engines/google.py index 47e6c007..46944015 100644 --- a/application/lib/ebook_tts/engines/google.py +++ b/application/lib/ebook_tts/engines/google.py @@ -150,6 +150,7 @@ class GoogleWebTTSFree(TTSBase): request_interval = 10 #额外的,好像每天只允许50个请求 max_len_per_request = 1666 languages = gtts_languages + engine_url = 'https://translate.google.com' def __init__(self, params): super().__init__(params) @@ -184,6 +185,7 @@ class GoogleTextToSpeech(TTSBase): request_interval = 2 max_len_per_request = 1666 languages = googletts_languages + engine_url = 'https://console.cloud.google.com/apis/api/texttospeech.googleapis.com/overview' def __init__(self, params): super().__init__(params) diff --git a/application/lib/ebook_tts/engines/tts_base.py b/application/lib/ebook_tts/engines/tts_base.py index 480b7bc8..5d6e41c4 100644 --- a/application/lib/ebook_tts/engines/tts_base.py +++ b/application/lib/ebook_tts/engines/tts_base.py @@ -14,6 +14,7 @@ class TTSBase: max_len_per_request = 500 languages = {} regions = {} + engine_url = '' #一个链接,关于引擎的介绍链接网页 region_url = '' #一个链接,可以在这个链接网页上找到可用的区域 voice_url = '' #一个链接,可以在这个网页上找到语音名称列表 language_url = '' #一个链接,可以在这个网页上找到支持的语种列表 diff --git a/application/lib/ebook_tts/html_audiolator.py b/application/lib/ebook_tts/html_audiolator.py index d82e2c67..ad5087d0 100644 --- a/application/lib/ebook_tts/html_audiolator.py +++ b/application/lib/ebook_tts/html_audiolator.py @@ -11,9 +11,9 @@ def get_tts_engines(): for name, engine in builtin_tts_engines.items(): info[name] = {'alias': engine.alias, 'need_api_key': engine.need_api_key, 'default_api_host': engine.default_api_host, 'api_key_hint': engine.api_key_hint, - 'languages': engine.languages, 'region_url': engine.region_url, - 'voice_url': engine.voice_url, 'language_url': engine.language_url, - 'regions': engine.regions} + 'languages': engine.languages, 'engine_url': engine.engine_url, + 'region_url': engine.region_url, 'voice_url': engine.voice_url, + 'language_url': engine.language_url, 'regions': engine.regions} return info class HtmlAudiolator: diff --git a/application/lib/pymp3cat.py b/application/lib/pymp3cat.py index b1ac51b0..a4ac7d18 100644 --- a/application/lib/pymp3cat.py +++ b/application/lib/pymp3cat.py @@ -1,8 +1,11 @@ #!/usr/bin/env python3 # -*- coding:utf-8 -*- +#Author: cdhigh #合并mp3文件 -#来源:https://github.com/dmulholl/mp3cat -#将go语言转换为python,方便类似GAE这样不能执行二进制文件的平台合并mp3 +#原始来源:https://github.com/dmulholl/mp3cat +#将go语言转换为python,没有第三方依赖,方便类似GAE这样不能执行二进制文件的平台合并mp3 +#因为不需要解码,只是将源文件中的音乐帧逐个拷贝到目标文件, +#速度尽管慢一些(大概是go版本的2到3倍时间),在可以接受的范围内 import os, io, struct #版本 @@ -21,21 +24,21 @@ DualChannel = 2 Mono = 3 -#位率对应表 -v1_br = { - 3: (0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448), #layer1 - 2: (0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384), #layer2 - 1: (0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320), #layer3 -} +#位率对应表[layer][bitRateIndex] +v1_br = [(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), + (0, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000, 0), #layer3 + (0, 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000, 384000, 0), #layer2 + (0, 32000, 64000, 96000, 128000, 160000, 192000, 224000, 256000, 288000, 320000, 352000, 384000, 416000, 448000, 0), #layer1 +] -v2_br = { - 3: (0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256), - 2: (0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160), - 1: (0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160), -} +v2_br = [(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), + (0, 8000, 16000, 24000, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, 0), + (0, 8000, 16000, 24000, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, 0), + (0, 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, 176000, 192000, 224000, 256000, 0), +] -#采样率对应表[ver][layer] -samplingTable = ((11025, 12000, 8000), (0, 0, 0), (22050, 24000, 16000), (44100, 48000, 32000)) +#采样率对应表[ver][samplingRateIndex] +samplingTable = ((11025, 12000, 8000, 0), (0, 0, 0, 0), (22050, 24000, 16000, 0), (44100, 48000, 32000, 0)) #每帧的采样数对应表[ver][layer] sampleCountTable = ((0, 576, 1152, 384), (0, 0, 0, 0), (0, 576, 1152, 384), (0, 1152, 1152, 384)) @@ -77,7 +80,7 @@ def NextObject(stream): #ID3v1标识: 'TAG',包括标签头在内,一共128字节 if header1[0:3] == b'TAG': stream.seek(start + 128) - return {'type': 'TAG', 'start': start, 'end': stream.tell(), 'len': 128} + return {'type': 'TAG', 'len': 128} #'start': start, 'end': stream.tell(), elif header1[0:3] == b'ID3': #ID3V2头一共10个字节 #char Header[3]; #ID3 @@ -94,17 +97,17 @@ def NextObject(stream): stream.seek(start) frame = {'type': 'ID3', 'len': length} - frame['start'] = start + #frame['start'] = start frame['raw'] = stream.read(length + 10) #长度不包含头部10个字节 - frame['end'] = stream.tell() + #frame['end'] = stream.tell() return frame elif (header1[0] == 0xff) and ((header1[1] & 0xe0) == 0xe0): #11比特的1,一个音乐数据帧开始 frame = ParseMusicHeader(header1) if frame: stream.seek(start) - frame['start'] = start + #frame['start'] = start frame['raw'] = stream.read(frame['len']) #帧长度包含头部4个字节 - frame['end'] = stream.tell() + #frame['end'] = stream.tell() return frame #出错,往后跳一个字节再重新尝试 @@ -153,38 +156,32 @@ def ParseMusicHeader(header): if mpegVer == MPEGVersionReserved: return None layer = (header[1] & 0x06) >> 1 #2位,层, 0-未使用,1-Layer3, 2-Layer2, 3-Layer3 - if layer == 0: - return None - crcProt = (header[1] & 0x01) == 0x00 #是否有CRC校验,0-校验 + #crcProt = (header[1] & 0x01) == 0x00 #是否有CRC校验,0-校验 bitRateIndex = (header[2] & 0xf0) >> 4 #位率索引,共4位 - if bitRateIndex == 0 or bitRateIndex == 15: - return None - - #查表得出位率 if mpegVer == MPEGVersion1: - bitRate = v1_br.get(layer)[bitRateIndex] * 1000 + bitRate = v1_br[layer][bitRateIndex] #查表得出位率 else: - bitRate = v2_br.get(layer)[bitRateIndex] * 1000 + bitRate = v2_br[layer][bitRateIndex] + if bitRate == 0: + return None samplingRateIndex = (header[2] & 0x0c) >> 2 #采样率索引,2位 - if samplingRateIndex == 3: + samplingRate = samplingTable[mpegVer][samplingRateIndex] #查表得出采样率 + if samplingRate == 0: return None - #查表得出采样率 - samplingRate = samplingTable[mpegVer][samplingRateIndex] - paddingBit = (header[2] & 0x02) == 0x02 #帧长调节 (1 bit) - privateBit = (header[2] & 0x01) == 0x01 #保留字 (1 bit) + #privateBit = (header[2] & 0x01) == 0x01 #保留字 (1 bit) channelMode = (header[3] & 0xc0) >> 6 #声道模式 (2 bits) - modeExtension = (header[3] & 0x30) >> 4 #扩充模式,仅用于 Joint Stereo mode. (2 bits) - if (channelMode != JointStereo) and (modeExtension != 0): - return None - - copyrightBit = (header[3] & 0x08) == 0x08 #版权 (1 bit) - originalBit = (header[3] & 0x04) == 0x04 #原版标志 (1 bit) - emphasis = (header[3] & 0x03) #强调标识 (2 bits) - if emphasis == 2: - return None + #modeExtension = (header[3] & 0x30) >> 4 #扩充模式,仅用于 Joint Stereo mode. (2 bits) + #if (channelMode != JointStereo) and (modeExtension != 0): + # return None + + #copyrightBit = (header[3] & 0x08) == 0x08 #版权 (1 bit) + #originalBit = (header[3] & 0x04) == 0x04 #原版标志 (1 bit) + #emphasis = (header[3] & 0x03) #强调标识 (2 bits) + #if emphasis == 2: + # return None #帧大小即每帧的采样数,表示一帧数据中采样的个数 sampleCount = sampleCountTable[mpegVer][layer] @@ -208,8 +205,12 @@ def ParseMusicHeader(header): # Experimentation on mp3 files captured from the wild indicates that it # includes the header at least. frameLength = int((sampleCount / 8) * bitRate / samplingRate + padding) - return {'type': 'FRAME', 'len': frameLength, 'bitRate': bitRate, 'samplingRate': samplingRate, - 'sampleCount': sampleCount, 'mpegVer': mpegVer, 'layer': layer, 'channelMode': channelMode} + + #每帧持续时间(毫秒) = 每帧采样数 / 采样频率 * 1000 + #duration = sampleCount / samplingRate * 1000 + + return {'type': 'FRAME', 'len': frameLength, 'bitRate': bitRate, + 'mpegVer': mpegVer, 'layer': layer, 'channelMode': channelMode} #创建一个新的VBR帧 def NewXingHeader(totalFrames, totalBytes): @@ -274,21 +275,26 @@ def AddID3v2Tag(output, input_): #合并mp3文件 #output: 输出文件名或流对象 -#inputs: 输入文件名列表或二进制内容类别 +#inputs: 输入文件名列表或二进制内容列表 #tagIndex: 是否需要将第n个文件的ID3拷贝过来 +#useBuffer: 是否使用内存缓冲区,仅仅适用于传入的参数是文件名 #force: 是否覆盖目标文件 #quiet: 是否打印过程 -def merge(output: str, inputs: list, tagIndex: int=None, force: bool=True, quiet: bool=False): +#返回合并的文件数 +def merge(output: str, inputs: list, tagIndex=None, useBuffer=True, force=False, quiet=False): if not force and isinstance(output, str) and os.path.exists(output): print(f"Error: the file '{output}' already exists.") - return + return 0 if inputs and isinstance(inputs[0], str) and output in inputs: print(f'Error: the list of input files includes the output file.') - return + return 0 printInfo = (lambda x: x) if quiet else (lambda x: print(x)) - outputStream = open(output, 'wb') if isinstance(output, str) else output + if isinstance(output, str): + outputStream = io.BytesIO() if useBuffer else open(output, 'wb') + else: + outputStream = output totalFrames = 0 totalBytes = 0 @@ -296,16 +302,19 @@ def merge(output: str, inputs: list, tagIndex: int=None, force: bool=True, quiet firstBitRate = 0 isVBR = False for idx, input_ in enumerate(inputs): - needClose = False if isinstance(input_, str): printInfo(f' + {input_}') - input_ = open(input_, 'rb') - needClose = True + if useBuffer: + with open(input_, 'rb') as f: + inputStream = io.BytesIO(f.read()) + else: + inputStream = open(input_, 'rb') else: printInfo(f' + ') + inputStream = input_ isFirstFrame = True - for frame in IterFrame(input_): + for frame in IterFrame(inputStream): if isFirstFrame: #第一个帧如果是VBR,不包含音乐数据 isFirstFrame = False if IsVBRHeader(frame): @@ -320,34 +329,58 @@ def merge(output: str, inputs: list, tagIndex: int=None, force: bool=True, quiet totalFrames += 1 totalBytes += frame['len'] totalFiles += 1 - if needClose: - input_.close() + + if isinstance(input_, str) and not useBuffer: + inputStream.close() if isinstance(output, str): - outputStream.close() + if useBuffer: + outputStream.seek(0) + else: + outputStream.close() #如果不同的文件的比特率不同,则在前面添加一个VBR头 if isVBR: printInfo("• Multiple bitrates detected. Adding VBR header.") - AddXingHeader(output, totalFrames, totalBytes) - if isinstance(output, str): - try: - tempStream.close() - os.remove(output + '.mp3cat.tmp') - except: - pass - - if tagIndex is not None and tagIndex < len(inputs): + if isinstance(output, str) and not useBuffer: + AddXingHeader(output, totalFrames, totalBytes) + else: + AddXingHeader(outputStream, totalFrames, totalBytes) + + #拷贝ID3Tag + if tagIndex is not None and (0 < tagIndex < len(inputs)): input_ = inputs[tagIndex] needClose = False if isinstance(input_, str): printInfo(f"• Copying ID3 tag from: {input_}") - input_ = open(input_, 'rb') - needClose = True + if useBuffer: + with open(input_, 'rb') as f: + inputStream = io.BytesIO(f.read()) + else: + inputStream = open(input_, 'rb') else: printInfo(f'• Copying ID3 tag from: ') - AddID3v2Tag(output, input_) - if needClose: - input_.close() + inputStream = input_ + if isinstance(output, str) and not useBuffer: + AddID3v2Tag(output, inputStream) + else: + AddID3v2Tag(outputStream, inputStream) + if isinstance(input_, str) and not useBuffer: + inputStream.close() + + if isinstance(output, str) and useBuffer: + with open(output, 'wb') as f: + f.write(outputStream.getvalue()) printInfo(f"• {totalFiles} files merged.") + return totalFiles + +if __name__ == '__main__': + import time + dir_ = 'd:/temp' + inputs = [os.path.join(dir_, f'Headspace{idx}.mp3') for idx in range(1, 9)] + inputs.append('d:/temp/na.mp3') + output = os.path.join(dir_, 'output.mp3') + startTime = time.time() + merge(output, inputs, force=True, useBuffer=True, tagIndex=8) + print(f'Consumed time: {time.time() - startTime:0.1f} seconds') diff --git a/application/static/base.js b/application/static/base.js index 513738e8..c1739a21 100644 --- a/application/static/base.js +++ b/application/static/base.js @@ -1362,27 +1362,24 @@ function TTSEngineFieldChanged(language, region) { $('#tts_region_div').hide(); } + let setTtsHref = function (target_id, url) { + let target = $(target_id); + if (url) { + target.attr('href', url); + target.removeAttr('onclick'); + target.css('text-decoration', 'underline dotted'); + } else { + target.attr('href', 'javascript:void(0)'); + target.attr('onclick', 'return false;'); + target.css('text-decoration', 'none'); + } + }; + //设置提示的链接 - let region_a = $('#tts_region_a'); - if (engine.region_url) { - region_a.attr('href', engine.region_url); - region_a.removeAttr('onclick'); - region_a.css('text-decoration', 'underline dotted'); - } else { - region_a.attr('href', 'javascript:void(0)'); - region_a.attr('onclick', 'return false;'); - region_a.css('text-decoration', 'none'); - } - let voice_a = $('#tts_voice_a'); - if (engine.voice_url) { - voice_a.attr('href', engine.voice_url); - voice_a.removeAttr('onclick'); - voice_a.css('text-decoration', 'underline dotted'); - } else { - voice_a.attr('href', 'javascript:void(0)'); - voice_a.attr('onclick', 'return false;'); - voice_a.css('text-decoration', 'none'); - } + setTtsHref('#tts_engine_a', engine.engine_url); + setTtsHref('#tts_region_a', engine.region_url); + setTtsHref('#tts_voice_a', engine.voice_url); + setTtsHref('#tts_language_a', engine.language_url); //更新语种代码 let tts_language_sel = $('#tts_language_sel'); diff --git a/application/templates/book_audiolator.html b/application/templates/book_audiolator.html index 3c5ce304..5291d08d 100644 --- a/application/templates/book_audiolator.html +++ b/application/templates/book_audiolator.html @@ -31,7 +31,8 @@
- + @@ -53,7 +54,8 @@
- + @@ -117,7 +119,7 @@

{{_("Test (Please save settings firstly)")}}

- +
diff --git a/application/templates/book_translator.html b/application/templates/book_translator.html index 9f3e9bdd..d2bab1d9 100644 --- a/application/templates/book_translator.html +++ b/application/templates/book_translator.html @@ -86,7 +86,7 @@

{{_("Test (Please save settings firstly)")}}

- +
diff --git a/application/translations/messages.pot b/application/translations/messages.pot index d6582b18..e0497cf3 100644 --- a/application/translations/messages.pot +++ b/application/translations/messages.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2024-04-18 21:52-0300\n" +"POT-Creation-Date: 2024-04-23 10:39-0300\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,7 +17,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.14.0\n" -#: application/templates/admin.html:3 application/templates/base.html:151 +#: application/templates/admin.html:3 application/templates/base.html:153 msgid "Admin" msgstr "" @@ -217,8 +217,8 @@ msgid "Host" msgstr "" #: application/templates/adv_archive.html:119 -#: application/templates/book_audiolator.html:77 -#: application/templates/book_translator.html:77 +#: application/templates/book_audiolator.html:112 +#: application/templates/book_translator.html:80 #: application/templates/setting.html:250 msgid "Save settings" msgstr "" @@ -428,7 +428,7 @@ msgid "Category" msgstr "" #: application/templates/base.html:35 -#: application/templates/book_audiolator.html:43 +#: application/templates/book_audiolator.html:57 #: application/templates/setting.html:130 msgid "Language" msgstr "" @@ -684,21 +684,29 @@ msgid "Emb" msgstr "" #: application/templates/base.html:98 +msgid "Tr" +msgstr "" + +#: application/templates/base.html:99 +msgid "Tts" +msgstr "" + +#: application/templates/base.html:100 msgid "" "The test email has been successfully sent to the following addresses. " "Please check your inbox or spam folder to confirm its delivery. Depending" " on your email server, there may be a slight delay." msgstr "" -#: application/templates/base.html:99 +#: application/templates/base.html:101 msgid "Translating..." msgstr "" -#: application/templates/base.html:100 +#: application/templates/base.html:102 msgid "The configuration validation is correct." msgstr "" -#: application/templates/base.html:101 application/templates/logs.html:22 +#: application/templates/base.html:103 application/templates/logs.html:22 #: application/templates/logs.html:67 application/templates/my.html:17 #: application/templates/setting.html:97 application/templates/setting.html:98 #: application/templates/setting.html:99 application/templates/setting.html:100 @@ -706,144 +714,193 @@ msgstr "" msgid "Title" msgstr "" -#: application/templates/base.html:102 +#: application/templates/base.html:104 #: application/templates/book_audiolator.html:3 -#: application/templates/book_audiolator.html:15 +#: application/templates/book_audiolator.html:20 msgid "Text to Speech" msgstr "" -#: application/templates/base.html:124 application/templates/home.html:12 +#: application/templates/base.html:126 application/templates/home.html:12 msgid "Logout" msgstr "" -#: application/templates/base.html:126 application/templates/home.html:17 +#: application/templates/base.html:128 application/templates/home.html:17 #: application/templates/login.html:3 application/templates/login.html:19 #: application/templates/login.html:29 msgid "Login" msgstr "" -#: application/templates/base.html:128 application/templates/signup.html:3 +#: application/templates/base.html:130 application/templates/signup.html:3 #: application/templates/signup.html:19 application/templates/signup.html:43 msgid "Signup" msgstr "" -#: application/templates/base.html:148 application/templates/home.html:11 +#: application/templates/base.html:150 application/templates/home.html:11 #: application/templates/my.html:3 msgid "Feeds" msgstr "" -#: application/templates/base.html:149 application/templates/setting.html:3 +#: application/templates/base.html:151 application/templates/setting.html:3 msgid "Settings" msgstr "" -#: application/templates/base.html:150 application/templates/logs.html:3 +#: application/templates/base.html:152 application/templates/logs.html:3 msgid "Logs" msgstr "" -#: application/templates/base.html:152 +#: application/templates/base.html:154 msgid "Advanced" msgstr "" -#: application/templates/base.html:153 application/templates/library.html:3 +#: application/templates/base.html:155 application/templates/library.html:3 msgid "Shared" msgstr "" -#: application/templates/book_audiolator.html:17 +#: application/templates/book_audiolator.html:22 #: application/templates/book_translator.html:19 msgid "State" msgstr "" -#: application/templates/book_audiolator.html:19 +#: application/templates/book_audiolator.html:24 msgid "Send Ebook and Audio" msgstr "" -#: application/templates/book_audiolator.html:20 +#: application/templates/book_audiolator.html:25 msgid "Send Audio only" msgstr "" -#: application/templates/book_audiolator.html:21 +#: application/templates/book_audiolator.html:26 msgid "Disable TTS" msgstr "" -#: application/templates/book_audiolator.html:25 +#: application/templates/book_audiolator.html:30 msgid "Send Audio To" msgstr "" -#: application/templates/book_audiolator.html:26 +#: application/templates/book_audiolator.html:31 msgid "Empty to use Kindle_email" msgstr "" -#: application/templates/book_audiolator.html:29 +#: application/templates/book_audiolator.html:35 msgid "TTS Engine" msgstr "" -#: application/templates/book_audiolator.html:35 +#: application/templates/book_audiolator.html:41 #: application/templates/book_translator.html:32 msgid "Api Host" msgstr "" -#: application/templates/book_audiolator.html:39 +#: application/templates/book_audiolator.html:42 +msgid "Empty to use default endpoint" +msgstr "" + +#: application/templates/book_audiolator.html:46 +msgid "Region" +msgstr "" + +#: application/templates/book_audiolator.html:53 #: application/templates/book_translator.html:36 msgid "Api Key" msgstr "" -#: application/templates/book_audiolator.html:49 -msgid "Voice Role" +#: application/templates/book_audiolator.html:65 +msgid "Voice name" msgstr "" -#: application/templates/book_audiolator.html:51 -msgid "Male" +#: application/templates/book_audiolator.html:72 +msgid "Voice speed" msgstr "" -#: application/templates/book_audiolator.html:52 -msgid "Female" +#: application/templates/book_audiolator.html:74 +msgid "Extra slow" msgstr "" -#: application/templates/book_audiolator.html:56 -msgid "Speed" +#: application/templates/book_audiolator.html:75 +msgid "Slow" msgstr "" -#: application/templates/book_audiolator.html:58 -msgid "Normal" +#: application/templates/book_audiolator.html:76 +#: application/templates/book_audiolator.html:86 +#: application/templates/book_audiolator.html:96 +msgid "Medium" msgstr "" -#: application/templates/book_audiolator.html:59 -msgid "Slow" +#: application/templates/book_audiolator.html:77 +msgid "Fast" msgstr "" -#: application/templates/book_audiolator.html:63 -msgid "Voice Style" +#: application/templates/book_audiolator.html:78 +msgid "Extra fast" msgstr "" -#: application/templates/book_audiolator.html:65 -msgid "Chat" +#: application/templates/book_audiolator.html:82 +msgid "Voice pitch" msgstr "" -#: application/templates/book_audiolator.html:66 -msgid "Cheerful" +#: application/templates/book_audiolator.html:84 +msgid "Extra low" msgstr "" -#: application/templates/book_audiolator.html:72 +#: application/templates/book_audiolator.html:85 +msgid "Low" +msgstr "" + +#: application/templates/book_audiolator.html:87 +msgid "High" +msgstr "" + +#: application/templates/book_audiolator.html:88 +msgid "Extra high" +msgstr "" + +#: application/templates/book_audiolator.html:92 +msgid "Voice volume" +msgstr "" + +#: application/templates/book_audiolator.html:94 +msgid "Extra soft" +msgstr "" + +#: application/templates/book_audiolator.html:95 +msgid "Soft" +msgstr "" + +#: application/templates/book_audiolator.html:97 +msgid "Loud" +msgstr "" + +#: application/templates/book_audiolator.html:98 +msgid "Extra loud" +msgstr "" + +#: application/templates/book_audiolator.html:104 #: application/templates/book_translator.html:72 msgid "Apply to all subscribed recipes" msgstr "" -#: application/templates/book_audiolator.html:83 -#: application/templates/book_translator.html:83 +#: application/templates/book_audiolator.html:109 +#: application/templates/book_translator.html:77 +msgid "" +"Note: Enabling this feature will significantly increase consumed CPU " +"instance hours." +msgstr "" + +#: application/templates/book_audiolator.html:118 +#: application/templates/book_translator.html:86 msgid "Test (Please save settings firstly)" msgstr "" -#: application/templates/book_audiolator.html:85 -#: application/templates/book_translator.html:85 +#: application/templates/book_audiolator.html:120 +#: application/templates/book_translator.html:88 msgid "Text" msgstr "" -#: application/templates/book_audiolator.html:91 +#: application/templates/book_audiolator.html:126 msgid "Your browser does not support the audio element." msgstr "" -#: application/templates/book_audiolator.html:96 -#: application/templates/book_translator.html:94 +#: application/templates/book_audiolator.html:131 +#: application/templates/book_translator.html:97 msgid "Test" msgstr "" @@ -903,7 +960,7 @@ msgstr "" msgid "Translated text style" msgstr "" -#: application/templates/book_translator.html:89 +#: application/templates/book_translator.html:92 msgid "Translation" msgstr "" @@ -1053,7 +1110,7 @@ msgid "Edge extension" msgstr "" #: application/templates/my.html:91 -msgid "Browser extensions also available." +msgid "Browser extensions also available" msgstr "" #: application/templates/reset_password.html:3 @@ -1238,9 +1295,9 @@ msgstr "" msgid "Never expire" msgstr "" -#: application/view/admin.py:51 application/view/adv.py:372 +#: application/view/admin.py:51 application/view/adv.py:371 #: application/view/setting.py:102 application/view/translator.py:83 -#: application/view/translator.py:161 +#: application/view/translator.py:163 msgid "Settings Saved!" msgstr "" @@ -1360,29 +1417,29 @@ msgstr "" msgid "Append qrcode of url to article" msgstr "" -#: application/view/adv.py:374 +#: application/view/adv.py:373 msgid "The format is invalid." msgstr "" -#: application/view/adv.py:407 +#: application/view/adv.py:406 msgid "Authorization Error!
{}" msgstr "" -#: application/view/adv.py:430 +#: application/view/adv.py:429 msgid "Success authorized by Pocket!" msgstr "" -#: application/view/adv.py:436 +#: application/view/adv.py:435 msgid "" "Failed to request authorization of Pocket!
See details " "below:

{}" msgstr "" -#: application/view/adv.py:458 +#: application/view/adv.py:457 msgid "The Instapaper service encountered an error. Please try again later." msgstr "" -#: application/view/adv.py:471 +#: application/view/adv.py:470 msgid "Request type [{}] unsupported" msgstr "" @@ -1403,9 +1460,9 @@ msgstr "" msgid "Cannot fetch data from {}, status: {}" msgstr "" -#: application/view/library.py:51 application/view/subscribe.py:203 -#: application/view/subscribe.py:323 application/view/subscribe.py:351 -#: application/view/subscribe.py:358 application/view/translator.py:29 +#: application/view/library.py:51 application/view/subscribe.py:212 +#: application/view/subscribe.py:332 application/view/subscribe.py:360 +#: application/view/subscribe.py:367 application/view/translator.py:29 msgid "The recipe does not exist." msgstr "" @@ -1487,111 +1544,111 @@ msgstr "" msgid "Title is requied!" msgstr "" -#: application/view/setting.py:174 +#: application/view/setting.py:175 msgid "Chinese" msgstr "" -#: application/view/setting.py:175 +#: application/view/setting.py:176 msgid "English" msgstr "" -#: application/view/setting.py:176 +#: application/view/setting.py:177 msgid "French" msgstr "" -#: application/view/setting.py:177 +#: application/view/setting.py:178 msgid "Spanish" msgstr "" -#: application/view/setting.py:178 +#: application/view/setting.py:179 msgid "Portuguese" msgstr "" -#: application/view/setting.py:179 +#: application/view/setting.py:180 msgid "German" msgstr "" -#: application/view/setting.py:180 +#: application/view/setting.py:181 msgid "Italian" msgstr "" -#: application/view/setting.py:181 +#: application/view/setting.py:182 msgid "Japanese" msgstr "" -#: application/view/setting.py:182 +#: application/view/setting.py:183 msgid "Russian" msgstr "" -#: application/view/setting.py:183 +#: application/view/setting.py:184 msgid "Turkish" msgstr "" -#: application/view/setting.py:184 +#: application/view/setting.py:185 msgid "Korean" msgstr "" -#: application/view/setting.py:185 +#: application/view/setting.py:186 msgid "Arabic" msgstr "" -#: application/view/setting.py:186 +#: application/view/setting.py:187 msgid "Czech" msgstr "" -#: application/view/setting.py:187 +#: application/view/setting.py:188 msgid "Dutch" msgstr "" -#: application/view/setting.py:188 +#: application/view/setting.py:189 msgid "Greek" msgstr "" -#: application/view/setting.py:189 +#: application/view/setting.py:190 msgid "Hindi" msgstr "" -#: application/view/setting.py:190 +#: application/view/setting.py:191 msgid "Malaysian" msgstr "" -#: application/view/setting.py:191 +#: application/view/setting.py:192 msgid "Bengali" msgstr "" -#: application/view/setting.py:192 +#: application/view/setting.py:193 msgid "Persian" msgstr "" -#: application/view/setting.py:193 +#: application/view/setting.py:194 msgid "Urdu" msgstr "" -#: application/view/setting.py:194 +#: application/view/setting.py:195 msgid "Swahili" msgstr "" -#: application/view/setting.py:195 +#: application/view/setting.py:196 msgid "Vietnamese" msgstr "" -#: application/view/setting.py:196 +#: application/view/setting.py:197 msgid "Punjabi" msgstr "" -#: application/view/setting.py:197 +#: application/view/setting.py:198 msgid "Javanese" msgstr "" -#: application/view/setting.py:198 +#: application/view/setting.py:199 msgid "Tagalog" msgstr "" -#: application/view/setting.py:199 +#: application/view/setting.py:200 msgid "Hausa" msgstr "" -#: application/view/share.py:54 application/view/subscribe.py:215 +#: application/view/share.py:54 application/view/subscribe.py:224 msgid "Unknown command: {}" msgstr "" @@ -1626,72 +1683,72 @@ msgstr "" msgid "Unknown: {}" msgstr "" -#: application/view/subscribe.py:58 +#: application/view/subscribe.py:67 msgid "Title or url is empty!" msgstr "" -#: application/view/subscribe.py:65 application/view/subscribe.py:133 +#: application/view/subscribe.py:74 application/view/subscribe.py:142 msgid "Duplicated subscription!" msgstr "" -#: application/view/subscribe.py:97 +#: application/view/subscribe.py:106 msgid "The Title or Url is empty." msgstr "" -#: application/view/subscribe.py:110 +#: application/view/subscribe.py:119 msgid "Failed to fetch the recipe." msgstr "" -#: application/view/subscribe.py:124 application/view/subscribe.py:285 +#: application/view/subscribe.py:133 application/view/subscribe.py:294 msgid "Failed to save the recipe. Error:" msgstr "" -#: application/view/subscribe.py:160 +#: application/view/subscribe.py:169 msgid "The Rss does not exist." msgstr "" -#: application/view/subscribe.py:241 +#: application/view/subscribe.py:250 msgid "You can only delete the uploaded recipe." msgstr "" -#: application/view/subscribe.py:245 +#: application/view/subscribe.py:254 msgid "The recipe have been subscribed, please unsubscribe it before delete." msgstr "" -#: application/view/subscribe.py:260 application/view/translator.py:50 +#: application/view/subscribe.py:269 application/view/translator.py:50 #: application/view/translator.py:99 application/view/translator.py:111 -#: application/view/translator.py:134 application/view/translator.py:177 -#: application/view/translator.py:189 +#: application/view/translator.py:134 application/view/translator.py:179 +#: application/view/translator.py:191 msgid "This recipe has not been subscribed to yet." msgstr "" -#: application/view/subscribe.py:272 +#: application/view/subscribe.py:281 msgid "Can not read uploaded file, Error:" msgstr "" -#: application/view/subscribe.py:280 +#: application/view/subscribe.py:289 msgid "" "Failed to decode the recipe. Please ensure that your recipe is saved in " "utf-8 encoding." msgstr "" -#: application/view/subscribe.py:300 +#: application/view/subscribe.py:309 msgid "The recipe is already in the library." msgstr "" -#: application/view/subscribe.py:330 +#: application/view/subscribe.py:339 msgid "The login information for this recipe has been cleared." msgstr "" -#: application/view/subscribe.py:334 +#: application/view/subscribe.py:343 msgid "The login information for this recipe has been saved." msgstr "" -#: application/view/translator.py:77 application/view/translator.py:155 +#: application/view/translator.py:77 application/view/translator.py:157 msgid "The api key is required." msgstr "" -#: application/view/translator.py:115 application/view/translator.py:193 +#: application/view/translator.py:115 application/view/translator.py:195 msgid "The text is empty." msgstr "" diff --git a/application/translations/tr_TR/LC_MESSAGES/messages.mo b/application/translations/tr_TR/LC_MESSAGES/messages.mo index cdf87e9f45d993c1691b8767d03b160e79c8ad3b..e31456318ce1ca95e47f09142acec30e6f4ca22c 100644 GIT binary patch delta 7178 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 delta 6498 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 diff --git a/application/translations/tr_TR/LC_MESSAGES/messages.po b/application/translations/tr_TR/LC_MESSAGES/messages.po index 6a0c2056..e7a12379 100644 --- a/application/translations/tr_TR/LC_MESSAGES/messages.po +++ b/application/translations/tr_TR/LC_MESSAGES/messages.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2024-04-18 21:52-0300\n" +"POT-Creation-Date: 2024-04-23 10:39-0300\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: tr_TR\n" @@ -18,7 +18,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.14.0\n" -#: application/templates/admin.html:3 application/templates/base.html:151 +#: application/templates/admin.html:3 application/templates/base.html:153 msgid "Admin" msgstr "Yönetim" @@ -218,8 +218,8 @@ msgid "Host" msgstr "Ana bilgisayar" #: application/templates/adv_archive.html:119 -#: application/templates/book_audiolator.html:77 -#: application/templates/book_translator.html:77 +#: application/templates/book_audiolator.html:112 +#: application/templates/book_translator.html:80 #: application/templates/setting.html:250 msgid "Save settings" msgstr "Ayarları kaydet" @@ -433,7 +433,7 @@ msgid "Category" msgstr "Kategori" #: application/templates/base.html:35 -#: application/templates/book_audiolator.html:43 +#: application/templates/book_audiolator.html:57 #: application/templates/setting.html:130 msgid "Language" msgstr "Dil" @@ -695,6 +695,14 @@ msgid "Emb" msgstr "Emb" #: application/templates/base.html:98 +msgid "Tr" +msgstr "Tr" + +#: application/templates/base.html:99 +msgid "Tts" +msgstr "Tts" + +#: application/templates/base.html:100 msgid "" "The test email has been successfully sent to the following addresses. " "Please check your inbox or spam folder to confirm its delivery. Depending" @@ -704,15 +712,15 @@ msgstr "" "gelen kutunuzu veya spam klasörünüzü kontrol edin. E-posta sunucunuza " "bağlı olarak hafif bir gecikme olabilir." -#: application/templates/base.html:99 +#: application/templates/base.html:101 msgid "Translating..." msgstr "Translating..." -#: application/templates/base.html:100 +#: application/templates/base.html:102 msgid "The configuration validation is correct." msgstr "Konfigürasyon doğrulaması doğru." -#: application/templates/base.html:101 application/templates/logs.html:22 +#: application/templates/base.html:103 application/templates/logs.html:22 #: application/templates/logs.html:67 application/templates/my.html:17 #: application/templates/setting.html:97 application/templates/setting.html:98 #: application/templates/setting.html:99 application/templates/setting.html:100 @@ -720,144 +728,193 @@ msgstr "Konfigürasyon doğrulaması doğru." msgid "Title" msgstr "Başlık" -#: application/templates/base.html:102 +#: application/templates/base.html:104 #: application/templates/book_audiolator.html:3 -#: application/templates/book_audiolator.html:15 +#: application/templates/book_audiolator.html:20 msgid "Text to Speech" msgstr "Metin Okuma" -#: application/templates/base.html:124 application/templates/home.html:12 +#: application/templates/base.html:126 application/templates/home.html:12 msgid "Logout" msgstr "Çıkış" -#: application/templates/base.html:126 application/templates/home.html:17 +#: application/templates/base.html:128 application/templates/home.html:17 #: application/templates/login.html:3 application/templates/login.html:19 #: application/templates/login.html:29 msgid "Login" msgstr "Giriş" -#: application/templates/base.html:128 application/templates/signup.html:3 +#: application/templates/base.html:130 application/templates/signup.html:3 #: application/templates/signup.html:19 application/templates/signup.html:43 msgid "Signup" msgstr "Kaydol" -#: application/templates/base.html:148 application/templates/home.html:11 +#: application/templates/base.html:150 application/templates/home.html:11 #: application/templates/my.html:3 msgid "Feeds" msgstr "RSSler" -#: application/templates/base.html:149 application/templates/setting.html:3 +#: application/templates/base.html:151 application/templates/setting.html:3 msgid "Settings" msgstr "Ayarlar" -#: application/templates/base.html:150 application/templates/logs.html:3 +#: application/templates/base.html:152 application/templates/logs.html:3 msgid "Logs" msgstr "Kayıtlar" -#: application/templates/base.html:152 +#: application/templates/base.html:154 msgid "Advanced" msgstr "Gelişmiş" -#: application/templates/base.html:153 application/templates/library.html:3 +#: application/templates/base.html:155 application/templates/library.html:3 msgid "Shared" msgstr "Paylaşılan" -#: application/templates/book_audiolator.html:17 +#: application/templates/book_audiolator.html:22 #: application/templates/book_translator.html:19 msgid "State" msgstr "Durum" -#: application/templates/book_audiolator.html:19 +#: application/templates/book_audiolator.html:24 msgid "Send Ebook and Audio" msgstr "E-kitap ve Ses Gönder" -#: application/templates/book_audiolator.html:20 +#: application/templates/book_audiolator.html:25 msgid "Send Audio only" msgstr "Sadece Ses Gönder" -#: application/templates/book_audiolator.html:21 +#: application/templates/book_audiolator.html:26 msgid "Disable TTS" msgstr "TTS'yi Devre Dışı Bırak" -#: application/templates/book_audiolator.html:25 +#: application/templates/book_audiolator.html:30 msgid "Send Audio To" msgstr "Sesi Gönder" -#: application/templates/book_audiolator.html:26 +#: application/templates/book_audiolator.html:31 msgid "Empty to use Kindle_email" msgstr "Kindle_email kullanmak için boş bırakın" -#: application/templates/book_audiolator.html:29 +#: application/templates/book_audiolator.html:35 msgid "TTS Engine" msgstr "TTS Motoru" -#: application/templates/book_audiolator.html:35 +#: application/templates/book_audiolator.html:41 #: application/templates/book_translator.html:32 msgid "Api Host" msgstr "Api Host" -#: application/templates/book_audiolator.html:39 +#: application/templates/book_audiolator.html:42 +msgid "Empty to use default endpoint" +msgstr "Varsayılan uç noktayı kullanmak için boş bırakın" + +#: application/templates/book_audiolator.html:46 +msgid "Region" +msgstr "Bölge" + +#: application/templates/book_audiolator.html:53 #: application/templates/book_translator.html:36 msgid "Api Key" msgstr "Api Key" -#: application/templates/book_audiolator.html:49 -msgid "Voice Role" -msgstr "Ses Rolü" - -#: application/templates/book_audiolator.html:51 -msgid "Male" -msgstr "Erkek" - -#: application/templates/book_audiolator.html:52 -msgid "Female" -msgstr "Kadın" +#: application/templates/book_audiolator.html:65 +msgid "Voice name" +msgstr "Ses adı" -#: application/templates/book_audiolator.html:56 -msgid "Speed" -msgstr "Hız" +#: application/templates/book_audiolator.html:72 +msgid "Voice speed" +msgstr "Ses hızı" -#: application/templates/book_audiolator.html:58 -msgid "Normal" -msgstr "Normal" +#: application/templates/book_audiolator.html:74 +msgid "Extra slow" +msgstr "Ekstra yavaş" -#: application/templates/book_audiolator.html:59 +#: application/templates/book_audiolator.html:75 msgid "Slow" msgstr "Yavaş" -#: application/templates/book_audiolator.html:63 -msgid "Voice Style" -msgstr "Ses Stili" +#: application/templates/book_audiolator.html:76 +#: application/templates/book_audiolator.html:86 +#: application/templates/book_audiolator.html:96 +msgid "Medium" +msgstr "Orta" -#: application/templates/book_audiolator.html:65 -msgid "Chat" -msgstr "Sohbet" +#: application/templates/book_audiolator.html:77 +msgid "Fast" +msgstr "Hızlı" -#: application/templates/book_audiolator.html:66 -msgid "Cheerful" -msgstr "Neşeli" +#: application/templates/book_audiolator.html:78 +msgid "Extra fast" +msgstr "Ekstra hızlı" -#: application/templates/book_audiolator.html:72 +#: application/templates/book_audiolator.html:82 +msgid "Voice pitch" +msgstr "Ses tonu" + +#: application/templates/book_audiolator.html:84 +msgid "Extra low" +msgstr "Ekstra düşük" + +#: application/templates/book_audiolator.html:85 +msgid "Low" +msgstr "Düşük" + +#: application/templates/book_audiolator.html:87 +msgid "High" +msgstr "Yüksek" + +#: application/templates/book_audiolator.html:88 +msgid "Extra high" +msgstr "Ekstra yüksek" + +#: application/templates/book_audiolator.html:92 +msgid "Voice volume" +msgstr "Ses hacmi" + +#: application/templates/book_audiolator.html:94 +msgid "Extra soft" +msgstr "Ekstra yumuşak" + +#: application/templates/book_audiolator.html:95 +msgid "Soft" +msgstr "Yumuşak" + +#: application/templates/book_audiolator.html:97 +msgid "Loud" +msgstr "Yüksek sesli" + +#: application/templates/book_audiolator.html:98 +msgid "Extra loud" +msgstr "Ekstra yüksek sesli" + +#: application/templates/book_audiolator.html:104 #: application/templates/book_translator.html:72 msgid "Apply to all subscribed recipes" msgstr "Tüm abone olunan tarifelere uygula" -#: application/templates/book_audiolator.html:83 -#: application/templates/book_translator.html:83 +#: application/templates/book_audiolator.html:109 +#: application/templates/book_translator.html:77 +msgid "" +"Note: Enabling this feature will significantly increase consumed CPU " +"instance hours." +msgstr "Not: Bu özelliği etkinleştirmek, kullanılan CPU örnek saatlerini önemli ölçüde artırır." + +#: application/templates/book_audiolator.html:118 +#: application/templates/book_translator.html:86 msgid "Test (Please save settings firstly)" msgstr "Test (Lütfen önce ayarları kaydedin)" -#: application/templates/book_audiolator.html:85 -#: application/templates/book_translator.html:85 +#: application/templates/book_audiolator.html:120 +#: application/templates/book_translator.html:88 msgid "Text" msgstr "Metin" -#: application/templates/book_audiolator.html:91 +#: application/templates/book_audiolator.html:126 msgid "Your browser does not support the audio element." msgstr "Tarayıcınız ses öğesini desteklemiyor." -#: application/templates/book_audiolator.html:96 -#: application/templates/book_translator.html:94 +#: application/templates/book_audiolator.html:131 +#: application/templates/book_translator.html:97 msgid "Test" msgstr "Test" @@ -917,7 +974,7 @@ msgstr "Orijinal metin stili" msgid "Translated text style" msgstr "Çevrilmiş metin stili" -#: application/templates/book_translator.html:89 +#: application/templates/book_translator.html:92 msgid "Translation" msgstr "Çeviri" @@ -1076,8 +1133,8 @@ msgid "Edge extension" msgstr "Edge eklentisi" #: application/templates/my.html:91 -msgid "Browser extensions also available." -msgstr "Tarayıcı eklentileri de mevcut." +msgid "Browser extensions also available" +msgstr "Tarayıcı eklentileri de mevcut" #: application/templates/reset_password.html:3 #: application/templates/reset_password.html:41 @@ -1268,9 +1325,9 @@ msgstr "Kullanıcı hesabı" msgid "Never expire" msgstr "hiç sona ermeyen" -#: application/view/admin.py:51 application/view/adv.py:372 +#: application/view/admin.py:51 application/view/adv.py:371 #: application/view/setting.py:102 application/view/translator.py:83 -#: application/view/translator.py:161 +#: application/view/translator.py:163 msgid "Settings Saved!" msgstr "Ayarlar Kaydedildi!" @@ -1390,19 +1447,19 @@ msgstr "Tarayıcıda aç" msgid "Append qrcode of url to article" msgstr "Makaleye URL'nin QR kodunu ekle" -#: application/view/adv.py:374 +#: application/view/adv.py:373 msgid "The format is invalid." msgstr "Format geçersiz." -#: application/view/adv.py:407 +#: application/view/adv.py:406 msgid "Authorization Error!
{}" msgstr "Yetkilendirme Hatası!
{}" -#: application/view/adv.py:430 +#: application/view/adv.py:429 msgid "Success authorized by Pocket!" msgstr "Pocket tarafından yetkilendirilen başarı!" -#: application/view/adv.py:436 +#: application/view/adv.py:435 msgid "" "Failed to request authorization of Pocket!
See details " "below:

{}" @@ -1410,13 +1467,13 @@ msgstr "" "Pocket yetkilendirme isteği başarısız oldu!
Aşağıdaki ayrıntılara " "bakın:

{}" -#: application/view/adv.py:458 +#: application/view/adv.py:457 msgid "The Instapaper service encountered an error. Please try again later." msgstr "" "Instapaper servisi bir hata ile karşılaştı. Lütfen daha sonra tekrar " "deneyin." -#: application/view/adv.py:471 +#: application/view/adv.py:470 msgid "Request type [{}] unsupported" msgstr "İstek türü [{}] desteklenmiyor" @@ -1437,9 +1494,9 @@ msgstr "Teslim edilecek tarif yok." msgid "Cannot fetch data from {}, status: {}" msgstr "{}, durumundan veri alınamıyor: {}" -#: application/view/library.py:51 application/view/subscribe.py:203 -#: application/view/subscribe.py:323 application/view/subscribe.py:351 -#: application/view/subscribe.py:358 application/view/translator.py:29 +#: application/view/library.py:51 application/view/subscribe.py:212 +#: application/view/subscribe.py:332 application/view/subscribe.py:360 +#: application/view/subscribe.py:367 application/view/translator.py:29 msgid "The recipe does not exist." msgstr "Tarif mevcut değil." @@ -1527,111 +1584,111 @@ msgstr "Kindle E-mail adresi gerekli!" msgid "Title is requied!" msgstr "Başlık zorunlu!" -#: application/view/setting.py:174 +#: application/view/setting.py:175 msgid "Chinese" msgstr "Çince" -#: application/view/setting.py:175 +#: application/view/setting.py:176 msgid "English" msgstr "İngilizce" -#: application/view/setting.py:176 +#: application/view/setting.py:177 msgid "French" msgstr "Fransızca" -#: application/view/setting.py:177 +#: application/view/setting.py:178 msgid "Spanish" msgstr "İspanyolca" -#: application/view/setting.py:178 +#: application/view/setting.py:179 msgid "Portuguese" msgstr "Portekizce" -#: application/view/setting.py:179 +#: application/view/setting.py:180 msgid "German" msgstr "Almanca" -#: application/view/setting.py:180 +#: application/view/setting.py:181 msgid "Italian" msgstr "İtalyanca" -#: application/view/setting.py:181 +#: application/view/setting.py:182 msgid "Japanese" msgstr "Japonca" -#: application/view/setting.py:182 +#: application/view/setting.py:183 msgid "Russian" msgstr "Rusça" -#: application/view/setting.py:183 +#: application/view/setting.py:184 msgid "Turkish" msgstr "Türkçe" -#: application/view/setting.py:184 +#: application/view/setting.py:185 msgid "Korean" msgstr "Koreli" -#: application/view/setting.py:185 +#: application/view/setting.py:186 msgid "Arabic" msgstr "Arapça" -#: application/view/setting.py:186 +#: application/view/setting.py:187 msgid "Czech" msgstr "Çek" -#: application/view/setting.py:187 +#: application/view/setting.py:188 msgid "Dutch" msgstr "Flemenkçe" -#: application/view/setting.py:188 +#: application/view/setting.py:189 msgid "Greek" msgstr "Yunan" -#: application/view/setting.py:189 +#: application/view/setting.py:190 msgid "Hindi" msgstr "Hintçe" -#: application/view/setting.py:190 +#: application/view/setting.py:191 msgid "Malaysian" msgstr "Malezyalı" -#: application/view/setting.py:191 +#: application/view/setting.py:192 msgid "Bengali" msgstr "Bengal" -#: application/view/setting.py:192 +#: application/view/setting.py:193 msgid "Persian" msgstr "Farsça" -#: application/view/setting.py:193 +#: application/view/setting.py:194 msgid "Urdu" msgstr "Urduca" -#: application/view/setting.py:194 +#: application/view/setting.py:195 msgid "Swahili" msgstr "Svahili" -#: application/view/setting.py:195 +#: application/view/setting.py:196 msgid "Vietnamese" msgstr "Vietnam" -#: application/view/setting.py:196 +#: application/view/setting.py:197 msgid "Punjabi" msgstr "Pencap" -#: application/view/setting.py:197 +#: application/view/setting.py:198 msgid "Javanese" msgstr "Cava" -#: application/view/setting.py:198 +#: application/view/setting.py:199 msgid "Tagalog" msgstr "Tagalog" -#: application/view/setting.py:199 +#: application/view/setting.py:200 msgid "Hausa" msgstr "Hausa" -#: application/view/share.py:54 application/view/subscribe.py:215 +#: application/view/share.py:54 application/view/subscribe.py:224 msgid "Unknown command: {}" msgstr "Bilinmeyen komut: {}" @@ -1666,50 +1723,50 @@ msgstr "Aşağıdaki ayrıntılara bakın:" msgid "Unknown: {}" msgstr "Bilinmeyen: {}" -#: application/view/subscribe.py:58 +#: application/view/subscribe.py:67 msgid "Title or url is empty!" msgstr "URL veya başlık hatalı" -#: application/view/subscribe.py:65 application/view/subscribe.py:133 +#: application/view/subscribe.py:74 application/view/subscribe.py:142 msgid "Duplicated subscription!" msgstr "Duplicated subscription!" -#: application/view/subscribe.py:97 +#: application/view/subscribe.py:106 msgid "The Title or Url is empty." msgstr "Başlık veya URL boş." -#: application/view/subscribe.py:110 +#: application/view/subscribe.py:119 msgid "Failed to fetch the recipe." msgstr "Tarif alınamadı." -#: application/view/subscribe.py:124 application/view/subscribe.py:285 +#: application/view/subscribe.py:133 application/view/subscribe.py:294 msgid "Failed to save the recipe. Error:" msgstr "Tarif kaydedilemedi. Hata:" -#: application/view/subscribe.py:160 +#: application/view/subscribe.py:169 msgid "The Rss does not exist." msgstr "Rss mevcut değil." -#: application/view/subscribe.py:241 +#: application/view/subscribe.py:250 msgid "You can only delete the uploaded recipe." msgstr "Yalnızca yüklenen tarifi silebilirsiniz." -#: application/view/subscribe.py:245 +#: application/view/subscribe.py:254 msgid "The recipe have been subscribed, please unsubscribe it before delete." msgstr "Tarif abone olunmuş, silmeden önce aboneliği iptal edin." -#: application/view/subscribe.py:260 application/view/translator.py:50 +#: application/view/subscribe.py:269 application/view/translator.py:50 #: application/view/translator.py:99 application/view/translator.py:111 -#: application/view/translator.py:134 application/view/translator.py:177 -#: application/view/translator.py:189 +#: application/view/translator.py:134 application/view/translator.py:179 +#: application/view/translator.py:191 msgid "This recipe has not been subscribed to yet." msgstr "Bu tarife henüz abone olunmadı." -#: application/view/subscribe.py:272 +#: application/view/subscribe.py:281 msgid "Can not read uploaded file, Error:" msgstr "Yüklenen dosya okunamıyor, Hata:" -#: application/view/subscribe.py:280 +#: application/view/subscribe.py:289 msgid "" "Failed to decode the recipe. Please ensure that your recipe is saved in " "utf-8 encoding." @@ -1717,23 +1774,22 @@ msgstr "" "Tarif çözümlenemedi. Lütfen tarifinizin utf-8 kodlamasında " "kaydedildiğinden emin olun." -#: application/view/subscribe.py:300 +#: application/view/subscribe.py:309 msgid "The recipe is already in the library." msgstr "Tarif zaten kütüphanede." -#: application/view/subscribe.py:330 +#: application/view/subscribe.py:339 msgid "The login information for this recipe has been cleared." msgstr "Bu tarifin giriş bilgileri temizlendi." -#: application/view/subscribe.py:334 +#: application/view/subscribe.py:343 msgid "The login information for this recipe has been saved." msgstr "Bu tarifin giriş bilgileri kaydedildi." -#: application/view/translator.py:77 application/view/translator.py:155 +#: application/view/translator.py:77 application/view/translator.py:157 msgid "The api key is required." msgstr "API anahtarı gereklidir." -#: application/view/translator.py:115 application/view/translator.py:193 +#: application/view/translator.py:115 application/view/translator.py:195 msgid "The text is empty." msgstr "Metin boş." - diff --git a/application/translations/zh/LC_MESSAGES/messages.mo b/application/translations/zh/LC_MESSAGES/messages.mo index 6f421331fcc1fc0a527de7585ec4bd041be320fa..01c5a771c714a18fd0441f269532dddae7fd84fe 100644 GIT binary patch delta 7107 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 diff --git a/application/translations/zh/LC_MESSAGES/messages.po b/application/translations/zh/LC_MESSAGES/messages.po index cc50c3b5..7713027d 100644 --- a/application/translations/zh/LC_MESSAGES/messages.po +++ b/application/translations/zh/LC_MESSAGES/messages.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: KindleEar v3.0.0\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2024-04-18 21:52-0300\n" +"POT-Creation-Date: 2024-04-23 10:39-0300\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh\n" @@ -18,7 +18,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.14.0\n" -#: application/templates/admin.html:3 application/templates/base.html:151 +#: application/templates/admin.html:3 application/templates/base.html:153 msgid "Admin" msgstr "账号管理" @@ -218,8 +218,8 @@ msgid "Host" msgstr "主机" #: application/templates/adv_archive.html:119 -#: application/templates/book_audiolator.html:77 -#: application/templates/book_translator.html:77 +#: application/templates/book_audiolator.html:112 +#: application/templates/book_translator.html:80 #: application/templates/setting.html:250 msgid "Save settings" msgstr "保存设置" @@ -429,7 +429,7 @@ msgid "Category" msgstr "类别" #: application/templates/base.html:35 -#: application/templates/book_audiolator.html:43 +#: application/templates/book_audiolator.html:57 #: application/templates/setting.html:130 msgid "Language" msgstr "语言" @@ -685,21 +685,29 @@ msgid "Emb" msgstr "Emb" #: application/templates/base.html:98 +msgid "Tr" +msgstr "Tr" + +#: application/templates/base.html:99 +msgid "Tts" +msgstr "Tts" + +#: application/templates/base.html:100 msgid "" "The test email has been successfully sent to the following addresses. " "Please check your inbox or spam folder to confirm its delivery. Depending" " on your email server, there may be a slight delay." msgstr "测试邮件已经成功发送到以下地址, 请打开收件箱或垃圾箱确认是否到达,根据服务器不同,可能稍有延迟。" -#: application/templates/base.html:99 +#: application/templates/base.html:101 msgid "Translating..." msgstr "正在翻译..." -#: application/templates/base.html:100 +#: application/templates/base.html:102 msgid "The configuration validation is correct." msgstr "配置校验正确。" -#: application/templates/base.html:101 application/templates/logs.html:22 +#: application/templates/base.html:103 application/templates/logs.html:22 #: application/templates/logs.html:67 application/templates/my.html:17 #: application/templates/setting.html:97 application/templates/setting.html:98 #: application/templates/setting.html:99 application/templates/setting.html:100 @@ -707,144 +715,193 @@ msgstr "配置校验正确。" msgid "Title" msgstr "书籍标题" -#: application/templates/base.html:102 +#: application/templates/base.html:104 #: application/templates/book_audiolator.html:3 -#: application/templates/book_audiolator.html:15 +#: application/templates/book_audiolator.html:20 msgid "Text to Speech" msgstr "文本转语音" -#: application/templates/base.html:124 application/templates/home.html:12 +#: application/templates/base.html:126 application/templates/home.html:12 msgid "Logout" msgstr "退出" -#: application/templates/base.html:126 application/templates/home.html:17 +#: application/templates/base.html:128 application/templates/home.html:17 #: application/templates/login.html:3 application/templates/login.html:19 #: application/templates/login.html:29 msgid "Login" msgstr "登录" -#: application/templates/base.html:128 application/templates/signup.html:3 +#: application/templates/base.html:130 application/templates/signup.html:3 #: application/templates/signup.html:19 application/templates/signup.html:43 msgid "Signup" msgstr "注册" -#: application/templates/base.html:148 application/templates/home.html:11 +#: application/templates/base.html:150 application/templates/home.html:11 #: application/templates/my.html:3 msgid "Feeds" msgstr "我的订阅" -#: application/templates/base.html:149 application/templates/setting.html:3 +#: application/templates/base.html:151 application/templates/setting.html:3 msgid "Settings" msgstr "设置" -#: application/templates/base.html:150 application/templates/logs.html:3 +#: application/templates/base.html:152 application/templates/logs.html:3 msgid "Logs" msgstr "投递日志" -#: application/templates/base.html:152 +#: application/templates/base.html:154 msgid "Advanced" msgstr "高级设置" -#: application/templates/base.html:153 application/templates/library.html:3 +#: application/templates/base.html:155 application/templates/library.html:3 msgid "Shared" msgstr "网友分享" -#: application/templates/book_audiolator.html:17 +#: application/templates/book_audiolator.html:22 #: application/templates/book_translator.html:19 msgid "State" msgstr "状态" -#: application/templates/book_audiolator.html:19 +#: application/templates/book_audiolator.html:24 msgid "Send Ebook and Audio" msgstr "推送电子书和音频" -#: application/templates/book_audiolator.html:20 +#: application/templates/book_audiolator.html:25 msgid "Send Audio only" msgstr "仅推送音频" -#: application/templates/book_audiolator.html:21 +#: application/templates/book_audiolator.html:26 msgid "Disable TTS" msgstr "禁止TTS" -#: application/templates/book_audiolator.html:25 +#: application/templates/book_audiolator.html:30 msgid "Send Audio To" msgstr "推送音频至" -#: application/templates/book_audiolator.html:26 +#: application/templates/book_audiolator.html:31 msgid "Empty to use Kindle_email" msgstr "留空则使用kindle_email" -#: application/templates/book_audiolator.html:29 +#: application/templates/book_audiolator.html:35 msgid "TTS Engine" msgstr "TTS引擎" -#: application/templates/book_audiolator.html:35 +#: application/templates/book_audiolator.html:41 #: application/templates/book_translator.html:32 msgid "Api Host" msgstr "主机" -#: application/templates/book_audiolator.html:39 +#: application/templates/book_audiolator.html:42 +msgid "Empty to use default endpoint" +msgstr "留空为使用默认端点" + +#: application/templates/book_audiolator.html:46 +msgid "Region" +msgstr "区域" + +#: application/templates/book_audiolator.html:53 #: application/templates/book_translator.html:36 msgid "Api Key" msgstr "Api Key" -#: application/templates/book_audiolator.html:49 -msgid "Voice Role" -msgstr "语音角色" - -#: application/templates/book_audiolator.html:51 -msgid "Male" -msgstr "男性" - -#: application/templates/book_audiolator.html:52 -msgid "Female" -msgstr "女性" +#: application/templates/book_audiolator.html:65 +msgid "Voice name" +msgstr "语音名字" -#: application/templates/book_audiolator.html:56 -msgid "Speed" -msgstr "速度" +#: application/templates/book_audiolator.html:72 +msgid "Voice speed" +msgstr "语音速度" -#: application/templates/book_audiolator.html:58 -msgid "Normal" -msgstr "正常" +#: application/templates/book_audiolator.html:74 +msgid "Extra slow" +msgstr "超级慢" -#: application/templates/book_audiolator.html:59 +#: application/templates/book_audiolator.html:75 msgid "Slow" msgstr "慢" -#: application/templates/book_audiolator.html:63 -msgid "Voice Style" -msgstr "语音类型" +#: application/templates/book_audiolator.html:76 +#: application/templates/book_audiolator.html:86 +#: application/templates/book_audiolator.html:96 +msgid "Medium" +msgstr "中等" -#: application/templates/book_audiolator.html:65 -msgid "Chat" -msgstr "聊天" +#: application/templates/book_audiolator.html:77 +msgid "Fast" +msgstr "快" -#: application/templates/book_audiolator.html:66 -msgid "Cheerful" -msgstr "兴奋" +#: application/templates/book_audiolator.html:78 +msgid "Extra fast" +msgstr "超级快" -#: application/templates/book_audiolator.html:72 +#: application/templates/book_audiolator.html:82 +msgid "Voice pitch" +msgstr "语音语调" + +#: application/templates/book_audiolator.html:84 +msgid "Extra low" +msgstr "超级低" + +#: application/templates/book_audiolator.html:85 +msgid "Low" +msgstr "低" + +#: application/templates/book_audiolator.html:87 +msgid "High" +msgstr "高" + +#: application/templates/book_audiolator.html:88 +msgid "Extra high" +msgstr "超级高" + +#: application/templates/book_audiolator.html:92 +msgid "Voice volume" +msgstr "语音音量" + +#: application/templates/book_audiolator.html:94 +msgid "Extra soft" +msgstr "超级低" + +#: application/templates/book_audiolator.html:95 +msgid "Soft" +msgstr "低" + +#: application/templates/book_audiolator.html:97 +msgid "Loud" +msgstr "高" + +#: application/templates/book_audiolator.html:98 +msgid "Extra loud" +msgstr "超级高" + +#: application/templates/book_audiolator.html:104 #: application/templates/book_translator.html:72 msgid "Apply to all subscribed recipes" msgstr "应用到当前所有已订阅Recipe" -#: application/templates/book_audiolator.html:83 -#: application/templates/book_translator.html:83 +#: application/templates/book_audiolator.html:109 +#: application/templates/book_translator.html:77 +msgid "" +"Note: Enabling this feature will significantly increase consumed CPU " +"instance hours." +msgstr "注意:启用此特性会显著增加CPU实例小时数的消耗。" + +#: application/templates/book_audiolator.html:118 +#: application/templates/book_translator.html:86 msgid "Test (Please save settings firstly)" msgstr "测试 (请先保存设置)" -#: application/templates/book_audiolator.html:85 -#: application/templates/book_translator.html:85 +#: application/templates/book_audiolator.html:120 +#: application/templates/book_translator.html:88 msgid "Text" msgstr "原文" -#: application/templates/book_audiolator.html:91 +#: application/templates/book_audiolator.html:126 msgid "Your browser does not support the audio element." msgstr "您的浏览器不支持audio标签。" -#: application/templates/book_audiolator.html:96 -#: application/templates/book_translator.html:94 +#: application/templates/book_audiolator.html:131 +#: application/templates/book_translator.html:97 msgid "Test" msgstr "测试" @@ -904,7 +961,7 @@ msgstr "原文样式" msgid "Translated text style" msgstr "译文样式" -#: application/templates/book_translator.html:89 +#: application/templates/book_translator.html:92 msgid "Translation" msgstr "译文" @@ -1054,8 +1111,8 @@ msgid "Edge extension" msgstr "Edge扩展程序" #: application/templates/my.html:91 -msgid "Browser extensions also available." -msgstr "浏览器扩展也有提供。" +msgid "Browser extensions also available" +msgstr "浏览器扩展也有提供" #: application/templates/reset_password.html:3 #: application/templates/reset_password.html:41 @@ -1241,9 +1298,9 @@ msgstr "账号" msgid "Never expire" msgstr "永久有效" -#: application/view/admin.py:51 application/view/adv.py:372 +#: application/view/admin.py:51 application/view/adv.py:371 #: application/view/setting.py:102 application/view/translator.py:83 -#: application/view/translator.py:161 +#: application/view/translator.py:163 msgid "Settings Saved!" msgstr "恭喜,保存成功!" @@ -1363,29 +1420,29 @@ msgstr "在浏览器打开" msgid "Append qrcode of url to article" msgstr "在每篇文章后附加文章链接的二维码" -#: application/view/adv.py:374 +#: application/view/adv.py:373 msgid "The format is invalid." msgstr "格式非法。" -#: application/view/adv.py:407 +#: application/view/adv.py:406 msgid "Authorization Error!
{}" msgstr "申请授权过程失败!
{}" -#: application/view/adv.py:430 +#: application/view/adv.py:429 msgid "Success authorized by Pocket!" msgstr "已经成功获得Pocket的授权!" -#: application/view/adv.py:436 +#: application/view/adv.py:435 msgid "" "Failed to request authorization of Pocket!
See details " "below:

{}" msgstr "申请Pocket授权失败!
错误信息参考如下:

{}" -#: application/view/adv.py:458 +#: application/view/adv.py:457 msgid "The Instapaper service encountered an error. Please try again later." msgstr "Instapaper服务器异常,请稍候再试。" -#: application/view/adv.py:471 +#: application/view/adv.py:470 msgid "Request type [{}] unsupported" msgstr "不支持你请求的命令类型 [{}]" @@ -1406,9 +1463,9 @@ msgstr "没有需要推送的Recipe。" msgid "Cannot fetch data from {}, status: {}" msgstr "无法从 {} 获取数据,状态: {}" -#: application/view/library.py:51 application/view/subscribe.py:203 -#: application/view/subscribe.py:323 application/view/subscribe.py:351 -#: application/view/subscribe.py:358 application/view/translator.py:29 +#: application/view/library.py:51 application/view/subscribe.py:212 +#: application/view/subscribe.py:332 application/view/subscribe.py:360 +#: application/view/subscribe.py:367 application/view/translator.py:29 msgid "The recipe does not exist." msgstr "此Recipe不存在。" @@ -1490,111 +1547,111 @@ msgstr "Kindle E-mail必须填写!" msgid "Title is requied!" msgstr "书籍标题不能为空!" -#: application/view/setting.py:174 +#: application/view/setting.py:175 msgid "Chinese" msgstr "中文" -#: application/view/setting.py:175 +#: application/view/setting.py:176 msgid "English" msgstr "英语" -#: application/view/setting.py:176 +#: application/view/setting.py:177 msgid "French" msgstr "法语" -#: application/view/setting.py:177 +#: application/view/setting.py:178 msgid "Spanish" msgstr "西班牙语" -#: application/view/setting.py:178 +#: application/view/setting.py:179 msgid "Portuguese" msgstr "葡萄牙语" -#: application/view/setting.py:179 +#: application/view/setting.py:180 msgid "German" msgstr "德语" -#: application/view/setting.py:180 +#: application/view/setting.py:181 msgid "Italian" msgstr "意大利语" -#: application/view/setting.py:181 +#: application/view/setting.py:182 msgid "Japanese" msgstr "日语" -#: application/view/setting.py:182 +#: application/view/setting.py:183 msgid "Russian" msgstr "俄语" -#: application/view/setting.py:183 +#: application/view/setting.py:184 msgid "Turkish" msgstr "土耳其语" -#: application/view/setting.py:184 +#: application/view/setting.py:185 msgid "Korean" msgstr "韩语" -#: application/view/setting.py:185 +#: application/view/setting.py:186 msgid "Arabic" msgstr "阿拉伯语" -#: application/view/setting.py:186 +#: application/view/setting.py:187 msgid "Czech" msgstr "捷克语" -#: application/view/setting.py:187 +#: application/view/setting.py:188 msgid "Dutch" msgstr "荷兰语" -#: application/view/setting.py:188 +#: application/view/setting.py:189 msgid "Greek" msgstr "希腊语" -#: application/view/setting.py:189 +#: application/view/setting.py:190 msgid "Hindi" msgstr "印地语" -#: application/view/setting.py:190 +#: application/view/setting.py:191 msgid "Malaysian" msgstr "马来西亚语" -#: application/view/setting.py:191 +#: application/view/setting.py:192 msgid "Bengali" msgstr "孟加拉语" -#: application/view/setting.py:192 +#: application/view/setting.py:193 msgid "Persian" msgstr "波斯语" -#: application/view/setting.py:193 +#: application/view/setting.py:194 msgid "Urdu" msgstr "乌尔都语" -#: application/view/setting.py:194 +#: application/view/setting.py:195 msgid "Swahili" msgstr "斯瓦希里语" -#: application/view/setting.py:195 +#: application/view/setting.py:196 msgid "Vietnamese" msgstr "越南语" -#: application/view/setting.py:196 +#: application/view/setting.py:197 msgid "Punjabi" msgstr "旁遮普语" -#: application/view/setting.py:197 +#: application/view/setting.py:198 msgid "Javanese" msgstr "爪哇语" -#: application/view/setting.py:198 +#: application/view/setting.py:199 msgid "Tagalog" msgstr "他加禄语" -#: application/view/setting.py:199 +#: application/view/setting.py:200 msgid "Hausa" msgstr "豪萨语" -#: application/view/share.py:54 application/view/subscribe.py:215 +#: application/view/share.py:54 application/view/subscribe.py:224 msgid "Unknown command: {}" msgstr "未知命令:{}" @@ -1629,72 +1686,72 @@ msgstr "下面是一些技术细节:" msgid "Unknown: {}" msgstr "未知: {}" -#: application/view/subscribe.py:58 +#: application/view/subscribe.py:67 msgid "Title or url is empty!" msgstr "标题或 URL 为空!" -#: application/view/subscribe.py:65 application/view/subscribe.py:133 +#: application/view/subscribe.py:74 application/view/subscribe.py:142 msgid "Duplicated subscription!" msgstr "重复的订阅!" -#: application/view/subscribe.py:97 +#: application/view/subscribe.py:106 msgid "The Title or Url is empty." msgstr "标题或URL为空。" -#: application/view/subscribe.py:110 +#: application/view/subscribe.py:119 msgid "Failed to fetch the recipe." msgstr "抓取Recipe失败。" -#: application/view/subscribe.py:124 application/view/subscribe.py:285 +#: application/view/subscribe.py:133 application/view/subscribe.py:294 msgid "Failed to save the recipe. Error:" msgstr "保存Recipe失败。错误:" -#: application/view/subscribe.py:160 +#: application/view/subscribe.py:169 msgid "The Rss does not exist." msgstr "此RSS不存在。" -#: application/view/subscribe.py:241 +#: application/view/subscribe.py:250 msgid "You can only delete the uploaded recipe." msgstr "您只能删除你自己上传的Recipe。" -#: application/view/subscribe.py:245 +#: application/view/subscribe.py:254 msgid "The recipe have been subscribed, please unsubscribe it before delete." msgstr "此Recipe已经被订阅,请先取消订阅然后再删除。" -#: application/view/subscribe.py:260 application/view/translator.py:50 +#: application/view/subscribe.py:269 application/view/translator.py:50 #: application/view/translator.py:99 application/view/translator.py:111 -#: application/view/translator.py:134 application/view/translator.py:177 -#: application/view/translator.py:189 +#: application/view/translator.py:134 application/view/translator.py:179 +#: application/view/translator.py:191 msgid "This recipe has not been subscribed to yet." msgstr "此Recipe尚未被订阅。" -#: application/view/subscribe.py:272 +#: application/view/subscribe.py:281 msgid "Can not read uploaded file, Error:" msgstr "无法读取上传的文件,错误:" -#: application/view/subscribe.py:280 +#: application/view/subscribe.py:289 msgid "" "Failed to decode the recipe. Please ensure that your recipe is saved in " "utf-8 encoding." msgstr "解码Recipe失败,请确保您的Recipe为utf-8编码。" -#: application/view/subscribe.py:300 +#: application/view/subscribe.py:309 msgid "The recipe is already in the library." msgstr "此Recipe已经在新闻源中。" -#: application/view/subscribe.py:330 +#: application/view/subscribe.py:339 msgid "The login information for this recipe has been cleared." msgstr "此Recipe的网站登录信息已经被删除。" -#: application/view/subscribe.py:334 +#: application/view/subscribe.py:343 msgid "The login information for this recipe has been saved." msgstr "此Recipe的网站登录信息已经保存。" -#: application/view/translator.py:77 application/view/translator.py:155 +#: application/view/translator.py:77 application/view/translator.py:157 msgid "The api key is required." msgstr "需要填写api key." -#: application/view/translator.py:115 application/view/translator.py:193 +#: application/view/translator.py:115 application/view/translator.py:195 msgid "The text is empty." msgstr "文本为空。" diff --git a/application/view/adv.py b/application/view/adv.py index 0de3faab..3bb15c4e 100644 --- a/application/view/adv.py +++ b/application/view/adv.py @@ -173,9 +173,18 @@ def AdvImportPost(): rssList = opml.from_string(upload.read()) except Exception as e: return adv_render_template('adv_import.html', 'import', user=user, tips=str(e)) + + #兼容老版本的转义 + isKindleEarOpml = False + ownerElem = rssList._tree.xpath('/opml/head/ownerName') + if ownerElem and ownerElem[0].text == 'KindleEar': + isKindleEarOpml = True for o in walkOpmlOutline(rssList): - title, url, isfulltext = xml_unescape(o.text or o.title), xml_unescape(o.xmlUrl), o.isFulltext #isFulltext为非标准属性 + if o.text and not o.title and isKindleEarOpml: #老版本只有text属性,没有title属性 + title, url, isfulltext = o.text, unquote_plus(o.xmlUrl), o.isFulltext #isFulltext为非标准属性 + else: + title, url, isfulltext = xml_unescape(o.text or o.title), xml_unescape(o.xmlUrl), o.isFulltext isfulltext = str_to_bool(isfulltext) if isfulltext else defaultIsFullText if not url.startswith('http'): @@ -222,6 +231,7 @@ def AdvExport(): {date} {date} KindleEar + KindleEar {appVer} {outLines} @@ -236,7 +246,7 @@ def AdvExport(): xml_escape(feed.title), xml_escape(feed.url), isfulltext)) outLines = '\n'.join(outLines) - opmlFile = opmlTpl.format(date=date, outLines=outLines).encode('utf-8') + opmlFile = opmlTpl.format(date=date, appVer=appVer, outLines=outLines).encode('utf-8') return send_file(io.BytesIO(opmlFile), mimetype="text/xml", as_attachment=True, download_name="KindleEar_subscription.xml") #在本地选择一个图片上传做为自定义RSS书籍的封面 diff --git a/application/view/translator.py b/application/view/translator.py index dbcf37ff..710eb2b9 100644 --- a/application/view/translator.py +++ b/application/view/translator.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding:utf-8 -*- #文本翻译器和文本转语音 -import json, base64 +import json, base64, secrets from functools import wraps from flask import Blueprint, render_template, request, url_for from flask_babel import gettext as _ @@ -52,7 +52,7 @@ def BookTranslatorRoute(recipeType, recipe, user, recipeId): engines = json.dumps(get_trans_engines(), separators=(',', ':')) return render_template('book_translator.html', tab="my", tips=tips, params=params, title=recipe.title, - recipeId=recipeId, engines=engines) + recipeId=recipeId, engines=engines, famous=secrets.choice(famous_quotes)) #修改书籍文本翻译器的设置 @bpTranslator.post("/translator/", endpoint='BookTranslatorPost') @@ -99,7 +99,8 @@ def BookTranslatorPost(recipeType, recipe, user, recipeId): tips = _('This recipe has not been subscribed to yet.') return render_template('book_translator.html', tab="my", tips=tips, params=params, title=recipe.title, - recipeId=recipeId, engines=json.dumps(engines, separators=(',', ':'))) + recipeId=recipeId, engines=json.dumps(engines, separators=(',', ':')), + famous=secrets.choice(famous_quotes)) #测试Recipe的文本翻译器设置是否正确 @bpTranslator.post("/translator/test/", endpoint='BookTranslatorTestPost') @@ -136,7 +137,7 @@ def BookTTSRoute(recipeType, recipe, user, recipeId): engines = json.dumps(get_tts_engines(), separators=(',', ':')) return render_template('book_audiolator.html', tab="my", tips=tips, params=params, title=recipe.title, - recipeId=recipeId, engines=engines) + recipeId=recipeId, engines=engines, famous=secrets.choice(famous_quotes)) #修改书籍TTS的设置 @bpTranslator.post("/tts/", endpoint='BookTTSPost') @@ -156,7 +157,8 @@ def BookTTSPost(recipeType, recipe, user, recipeId): if not params.get('api_key'): tips = _('The api key is required.') return render_template('book_audiolator.html', tab="my", tips=tips, params=params, title=recipe.title, - recipeId=recipeId, engines=json.dumps(engines, separators=(',', ':'))) + recipeId=recipeId, engines=json.dumps(engines, separators=(',', ':')), + famous=secrets.choice(famous_quotes)) else: params['api_host'] = '' @@ -179,7 +181,8 @@ def BookTTSPost(recipeType, recipe, user, recipeId): tips = _('This recipe has not been subscribed to yet.') return render_template('book_audiolator.html', tab="my", tips=tips, params=params, title=recipe.title, - recipeId=recipeId, engines=json.dumps(engines, separators=(',', ':'))) + recipeId=recipeId, engines=json.dumps(engines, separators=(',', ':')), + famous=secrets.choice(famous_quotes)) #测试Recipe的文本转语音TTS设置是否正确 @bpTranslator.post("/tts/test/", endpoint='BookTTSTestPost') @@ -203,3 +206,36 @@ def BookTTSTestPost(recipeType, recipe, user, recipeId): data['audio'] = base64.b64encode(data['audio']).decode('utf-8') return data + +famous_quotes = [ + "I have a dream. - Martin Luther King Jr.", + "Be the change that you wish to see in the world. - Mahatma Gandhi", + "To be, or not to be, that is the question. - William Shakespeare", + "I think, therefore I am. - René Descartes", + "Give me liberty, or give me death! - Patrick Henry", + "The only thing we have to fear is fear itself. - Franklin D. Roosevelt", + "Injustice anywhere is a threat to justice everywhere. - Martin Luther King Jr.", + "Ask not what your country can do for you; ask what you can do for your country. - John F. Kennedy", + "The only way to do great work is to love what you do. - Steve Jobs", + "Float like a butterfly, sting like a bee. - Muhammad Ali", + "I am the master of my fate, I am the captain of my soul. - William Ernest Henley", + "I have nothing to declare except my genius. - Oscar Wilde", + "You miss 100% of the shots you don't take. - Wayne Gretzky", + "All men are created equal. - Thomas Jefferson", + "The unexamined life is not worth living. - Socrates", + "To infinity and beyond! - Buzz Lightyear", + "The only thing that is constant is change. - Heraclitus", + "It does not do to dwell on dreams and forget to live. - J.K. Rowling", + "Love all, trust a few, do wrong to none. - William Shakespeare", + "Life is what happens when you're busy making other plans. - John Lennon", + "The greatest glory in living lies not in never falling, but in rising every time we fall. - Nelson Mandela", + "The only true wisdom is in knowing you know nothing. - Socrates", + "In the end, it's not the years in your life that count. It's the life in your years. - Abraham Lincoln", + "Darkness cannot drive out darkness; only light can do that. Hate cannot drive out hate; only love can do that. - Martin Luther King Jr.", + "The journey of a thousand miles begins with one step. - Lao Tzu", + "Imagination is more important than knowledge. - Albert Einstein", + "We must learn to live together as brothers or perish together as fools. - Martin Luther King Jr.", + "Do not dwell in the past, do not dream of the future, concentrate the mind on the present moment. - Buddha", + "Happiness is not something ready-made. It comes from your own actions. - Dalai Lama", + "Life is like riding a bicycle. To keep your balance, you must keep moving. - Albert Einstein" +] diff --git a/application/work/worker.py b/application/work/worker.py index 943d037c..65e2ecb2 100644 --- a/application/work/worker.py +++ b/application/work/worker.py @@ -99,15 +99,18 @@ def WorkerImpl(userName: str, recipeId: list=None, log=None): #如果有TTS音频,先推送音频 ext, audio = MergeAudioSegment(roList) if audio: + if lastSendTime and (time.time() - lastSendTime < 10): + time.sleep(10) + audioName = f'{title}.{ext}' to = roList[0].tts.get('send_to') or user.cfg('kindle_email') send_to_kindle(user, audioName, (audioName, audio), to=to) lastSendTime = time.time() + ret.append(f"Sent {title}.mp3") if book: #避免触发垃圾邮件机制,最短10s发送一次 - now = time.time() #单位为s - if lastSendTime and (now - lastSendTime < 10): + if lastSendTime and (time.time() - lastSendTime < 10): #单位为s time.sleep(10) send_to_kindle(user, title, book) @@ -157,22 +160,16 @@ def GetAllRecipeSrc(user, idList): #返回可用的mp3cat执行文件路径 def mp3cat_path(): - import subprocess, platform - mp3Cat = 'mp3cat' - isWindows = 'Windows' in platform.system() - execFile = 'mp3cat.exe' if isWindows else 'mp3cat' - try: #优先使用系统安装的mp3cat - subprocess.run([execFile, "--version"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True, shell=True) + try: + import subprocess, platform + isWindows = 'Windows' in platform.system() + execFile = 'mp3cat.exe' if isWindows else 'mp3cat' + subprocess.run([execFile, "--version"], check=True, shell=True) default_log.debug('Using system mp3cat') except: #subprocess.CalledProcessError: - mp3Cat = os.path.join(appDir, 'tools', 'mp3cat', execFile) - try: - subprocess.run([mp3Cat, "--version"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True, shell=True) - default_log.debug('Using app mp3cat') - except Exception as e: - #default_log.warning(f"Cannot execute mp3cat. Please check file exists and permissions: {e}") - mp3Cat = '' - return mp3Cat + #default_log.warning(f"Cannot execute mp3cat. Please check file exists and permissions: {e}") + execFile = '' + return execFile #合并TTS生成的音频片段 def MergeAudioSegment(roList): @@ -182,12 +179,12 @@ def MergeAudioSegment(roList): return ret mp3Cat = mp3cat_path() - pymp3cat = None - if not mp3Cat: + if mp3Cat: + import subprocess + else: import pymp3cat default_log.info('Using python version mp3cat') - import shutil, subprocess from calibre.ptempfile import PersistentTemporaryDirectory tempDir = PersistentTemporaryDirectory(prefix='ttsmerg_', dir=os.environ.get('KE_TEMP_DIR')) @@ -198,19 +195,23 @@ def MergeAudioSegment(roList): if not mp3Files: continue outputFile = os.path.join(tempDir, f'output_{idx:04d}.mp3') + mergedFiles = 0 if mp3Cat: + mergedFiles = len(mp3Files) mp3Files = ' '.join(mp3Files) runRet = subprocess.run(f'{mp3Cat} {mp3Files} -f -q -o {outputFile}', shell=True) - if (runRet.returncode == 0) and os.path.exists(outputFile): - chapters.append(outputFile) + if runRet.returncode != 0: + mergedFiles = 0 + info = f'mp3cat return code : {runRet.returncode}' else: try: - pymp3cat.merge(outputFile, mp3Files, quiet=True) - if os.path.exists(outputFile): - chapters.append(outputFile) + mergedFiles = pymp3cat.merge(outputFile, mp3Files, quiet=True) except Exception as e: default_log.warning('Failed to merge mp3 by pymp3cat: {e}') + if mergedFiles and os.path.exists(outputFile): + chapters.append(outputFile) + #再将所有recipe的音频合并为一个大的文件 if len(chapters) == 1: @@ -223,18 +224,21 @@ def MergeAudioSegment(roList): elif chapters: outputFile = os.path.join(tempDir, 'final.mp3') info = '' + mergedFiles = 0 if mp3Cat: + mergedFiles = len(chapters) mp3Files = ' '.join(chapters) runRet = subprocess.run(f'{mp3Cat} {mp3Files} -f -q -o {outputFile}', shell=True) if runRet.returncode != 0: + mergedFiles = 0 info = f'mp3cat return code : {runRet.returncode}' else: try: - pymp3cat.merge(outputFile, chapters, quiet=True) + mergedFiles = pymp3cat.merge(outputFile, chapters, quiet=True) except Exception as e: info = 'Failed merge mp3 by pymp3cat: {e}' - if not info and os.path.exists(outputFile): + if not info and mergedFiles and os.path.exists(outputFile): try: with open(outputFile, 'rb') as f: data = f.read() @@ -245,6 +249,7 @@ def MergeAudioSegment(roList): default_log.warning(info if info else 'Failed merge mp3') #清理临时文件 + import shutil for dir_ in [*audioDirs, tempDir]: try: shutil.rmtree(dir_)