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 cdf87e9f..e3145631 100644 Binary files a/application/translations/tr_TR/LC_MESSAGES/messages.mo and b/application/translations/tr_TR/LC_MESSAGES/messages.mo differ 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 6f421331..01c5a771 100644 Binary files a/application/translations/zh/LC_MESSAGES/messages.mo and b/application/translations/zh/LC_MESSAGES/messages.mo differ 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_)