Showing preview only (575K chars total). Download the full file or copy to clipboard to get everything.
Repository: CBIhalsen/PolyglotPDF
Branch: main
Commit: 87b3e94654be
Files: 56
Total size: 496.2 KB
Directory structure:
gitextract_n0dc3ude/
├── .dockerignore
├── .gitignore
├── All_Translation.py
├── Bing_translation.py
├── Deepl_Translation.py
├── Dockerfile
├── EbookTranslator/
│ ├── EbookTranslator/
│ │ ├── All_Translation.py
│ │ ├── Deepl_Translation.py
│ │ ├── LLMS_translation.py
│ │ ├── YouDao_translation.py
│ │ ├── __init__.py
│ │ ├── cli.py
│ │ ├── convert2pdf.py
│ │ ├── load_config.py
│ │ └── main_function.py
│ ├── LICENSE
│ ├── README.md
│ ├── requirements.txt
│ └── setup.py
├── LICENSE
├── LLMS_translation.py
├── OldMain.py
├── README.md
├── README_CN.md
├── README_JA.md
├── README_KO.md
├── README_TW.md
├── Subset_Font.py
├── YouDao_translation.py
├── app.py
├── build.py
├── config.json
├── convert2pdf.py
├── docker-compose.yml
├── download_model.py
├── get_new_blocks.py
├── index.html
├── languagedetect.py
├── load_config.py
├── main.py
├── merge_pdf.py
├── pdf_thumbnail.py
├── pdfviewer.html
├── pdfviewer2.html
├── recent.json
├── requirements.txt
├── static/
│ ├── 1.js
│ ├── 2.js
│ ├── 3.js
│ ├── 4.js
│ ├── i18n.js
│ ├── main.css
│ ├── setup.css
│ ├── setup.js
│ └── thumbnail/
│ └── ...txt
└── update_recent.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .dockerignore
================================================
# 忽略 Git 相关文件
.git
.gitignore
# 忽略 Python 缓存和编译文件
__pycache__/
**/__pycache__/
*.pyc
*.pyo
*.pyd
# 忽略虚拟环境相关文件夹
.Python
env/
venv/
# 忽略 pip 日志
pip-log.txt
pip-delete-this-directory.txt
# 忽略测试和覆盖率相关文件
.tox/
.coverage
.coverage.*
.cache/
nosetests.xml
coverage.xml
*.cover
# 忽略日志文件
*.log
logs/*.log
# 忽略 pytest 缓存
.pytest_cache/
# 忽略项目根目录下的 lib 文件夹
lib/
# 忽略 IDE 配置文件
.idea/
.vscode/
================================================
FILE: .gitignore
================================================
__pycache__
venv
*.local
================================================
FILE: All_Translation.py
================================================
import time
import os
import Deepl_Translation as dt
import YouDao_translation as yt
import Bing_translation as bt
import LLMS_translation as lt
import asyncio
from functools import wraps
import threading
from queue import Queue
# 创建一个信号量,限制并发为1(串行处理)
translation_semaphore = asyncio.Semaphore(1)
# 创建一个队列处理锁,确保队列操作线程安全
queue_lock = threading.Lock()
# 创建翻译请求队列
translation_queue = Queue()
# 标记队列处理器是否已启动
queue_processor_started = False
def retry_on_error(max_retries=2, delay=1):
def decorator(func):
@wraps(func)
def wrapper_sync(*args, **kwargs):
retries = 0
while retries <= max_retries:
try:
return func(*args, **kwargs)
except Exception as e:
retries += 1
if retries <= max_retries:
print(f"Error occurred: {str(e)}")
print(f"Retrying... (Attempt {retries} of {max_retries})")
time.sleep(delay)
else:
print(f"Max retries reached. Skipping... Final error: {str(e)}")
return None
return None
async def wrapper_async(*args, **kwargs):
retries = 0
while retries <= max_retries:
try:
return await func(*args, **kwargs)
except Exception as e:
retries += 1
if retries <= max_retries:
print(f"Error occurred: {str(e)}")
print(f"Retrying... (Attempt {retries} of {max_retries})")
await asyncio.sleep(delay)
else:
print(f"Max retries reached. Skipping... Final error: {str(e)}")
return None
return None
return wrapper_async if asyncio.iscoroutinefunction(func) else wrapper_sync
return decorator
# 队列处理器函数
def process_translation_queue():
global queue_processor_started
# 在这里只创建一次事件循环
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
while True:
task = translation_queue.get()
if task is None: # 终止信号
translation_queue.task_done()
break
try:
func, args, kwargs, result_holder = task
# 这里直接用上面创建的 loop 执行
result = loop.run_until_complete(func(*args, **kwargs))
result_holder['result'] = result
except Exception as e:
print(f"Error processing translation task: {str(e)}")
result_holder['result'] = None
finally:
translation_queue.task_done()
# 跳出循环后,才一次性关闭事件循环
# 先清理异步生成器
loop.run_until_complete(loop.shutdown_asyncgens())
# 然后再 close
loop.close()
# 启动队列处理线程
def ensure_queue_processor():
global queue_processor_started
with queue_lock:
if not queue_processor_started:
threading.Thread(target=process_translation_queue, daemon=True).start()
queue_processor_started = True
class Online_translation:
def __init__(self, original_language, target_language, translation_type, texts_to_process=[]):
self.model_name = f"opus-mt-{original_language}-{target_language}"
self.original_text = texts_to_process
self.target_language = target_language
self.original_lang = original_language
self.translation_type = translation_type
# 确保队列处理器已启动
ensure_queue_processor()
def run_async(self, coro):
# 创建结果容器
result_holder = {'result': None}
# 将协程包装为任务并放入队列
translation_queue.put((self._run_coro_with_semaphore, [coro], {}, result_holder))
# 等待任务完成
translation_queue.join()
# 返回结果
return result_holder['result']
async def _run_coro_with_semaphore(self, coro):
# 使用信号量确保串行执行
async with translation_semaphore:
return await coro
def translation(self):
print('翻译api', self.translation_type)
if self.translation_type == 'AI302':
translated_list = self.run_async(self.AI302_translation())
elif self.translation_type == 'deepl':
translated_list = self.deepl_translation()
elif self.translation_type == 'youdao':
translated_list = self.youdao_translation()
elif self.translation_type == 'bing':
translated_list = self.bing_translation()
elif self.translation_type == 'openai':
translated_list = self.run_async(self.openai_translation())
elif self.translation_type == 'deepseek':
translated_list = self.run_async(self.deepseek_translation())
elif self.translation_type == 'Doubao':
translated_list = self.run_async(self.Doubao_translation())
elif self.translation_type == 'Qwen':
translated_list = self.run_async(self.Qwen_translation())
elif self.translation_type == 'Grok':
translated_list = self.run_async(self.Grok_translation())
elif self.translation_type == 'ThirdParty':
translated_list = self.run_async(self.ThirdParty_translation())
elif self.translation_type == 'GLM':
translated_list = self.run_async(self.GLM_translation())
else:
translated_list = self.deepl_translation()
return translated_list
@retry_on_error()
def deepl_translation(self):
translated_texts = dt.translate(
texts=self.original_text,
original_lang=self.original_lang,
target_lang=self.target_language
)
return translated_texts
@retry_on_error()
def youdao_translation(self):
translated_texts = yt.translate(
texts=self.original_text,
original_lang=self.original_lang,
target_lang=self.target_language
)
return translated_texts
@retry_on_error()
def bing_translation(self):
try:
translated_texts = bt.translate(
texts=self.original_text,
original_lang=self.original_lang,
target_lang=self.target_language
)
print(f"Bing translation completed: {len(translated_texts)} texts processed")
return translated_texts
except Exception as e:
print(f"Error in Bing translation: {e}")
return [""] * len(self.original_text)
@retry_on_error()
async def AI302_translation(self):
translator = lt.AI302_translation()
translated_texts = await translator.translate(
texts=self.original_text,
original_lang=self.original_lang,
target_lang=self.target_language
)
return translated_texts
@retry_on_error()
async def openai_translation(self):
translator = lt.Openai_translation()
translated_texts = await translator.translate(
texts=self.original_text,
original_lang=self.original_lang,
target_lang=self.target_language
)
return translated_texts
@retry_on_error()
async def deepseek_translation(self):
translator = lt.Deepseek_translation()
translated_texts = await translator.translate(
texts=self.original_text,
original_lang=self.original_lang,
target_lang=self.target_language
)
return translated_texts
@retry_on_error()
async def Doubao_translation(self):
translator = lt.Doubao_translation()
translated_texts = await translator.translate(
texts=self.original_text,
original_lang=self.original_lang,
target_lang=self.target_language
)
return translated_texts
@retry_on_error()
async def Qwen_translation(self):
translator = lt.Qwen_translation()
translated_texts = await translator.translate(
texts=self.original_text,
original_lang=self.original_lang,
target_lang=self.target_language
)
return translated_texts
@retry_on_error()
async def Grok_translation(self):
translator = lt.Grok_translation()
try:
translated_texts = await translator.translate(
texts=self.original_text,
original_lang=self.original_lang,
target_lang=self.target_language
)
print(f"Grok translation completed: {len(translated_texts)} texts processed")
return translated_texts
except Exception as e:
print(f"Error in Grok translation: {e}")
return [""] * len(self.original_text)
@retry_on_error()
async def ThirdParty_translation(self):
translator = lt.ThirdParty_translation()
try:
translated_texts = await translator.translate(
texts=self.original_text,
original_lang=self.original_lang,
target_lang=self.target_language
)
print(f"ThirdParty translation completed: {len(translated_texts)} texts processed")
return translated_texts
except Exception as e:
print(f"Error in ThirdParty translation: {e}")
return [""] * len(self.original_text)
@retry_on_error()
async def GLM_translation(self):
translator = lt.GLM_translation()
try:
translated_texts = await translator.translate(
texts=self.original_text,
original_lang=self.original_lang,
target_lang=self.target_language
)
print(f"GLM translation completed: {len(translated_texts)} texts processed")
return translated_texts
except Exception as e:
print(f"Error in GLM translation: {e}")
return [""] * len(self.original_text)
# 确保程序退出前清理资源
import atexit
@atexit.register
def cleanup():
# 发送终止信号
if queue_processor_started:
translation_queue.put(None)
# 给队列处理器一些时间来处理终止信号
translation_queue.join()
t = time.time()
def split_text_to_fit_token_limit(text, encoder, index_text, max_length=280):
tokens = encoder.encode(text)
if len(tokens) <= max_length:
return [(text, len(tokens), index_text)]
split_points = [i for i, token in enumerate(tokens) if encoder.decode([token]).strip() in [' ', '.', '?', '!','!','?','。']]
parts = []
last_split = 0
for i, point in enumerate(split_points + [len(tokens)]):
if point - last_split > max_length:
part_tokens = tokens[last_split:split_points[i - 1]]
parts.append((encoder.decode(part_tokens), len(part_tokens), index_text))
last_split = split_points[i - 1]
elif i == len(split_points):
part_tokens = tokens[last_split:]
parts.append((encoder.decode(part_tokens), len(part_tokens), index_text))
return parts
def process_texts(texts, encoder):
processed_texts = []
for i, text in enumerate(texts):
sub_texts = split_text_to_fit_token_limit(text, encoder, i)
processed_texts.extend(sub_texts)
return processed_texts
def calculate_split_points(processed_texts, max_tokens=425):
split_points = []
current_tokens = 0
for i in range(len(processed_texts) - 1):
current_tokens = processed_texts[i][1]
next_tokens = processed_texts[i + 1][1]
if current_tokens + next_tokens > max_tokens:
split_points.append(i)
split_points.append(len(processed_texts) - 1)
return split_points
#
# def translate(texts,original_language,target_language):
# from transformers import pipeline, AutoTokenizer
#
# model_name = f"./opus-mt-{original_language}-{target_language}"
# pipe = pipeline("translation", model=model_name)
# tokenizer = AutoTokenizer.from_pretrained(model_name)
#
# result = pipe(texts)
#
# result_values = [d['translation_text'] for d in result]
#
# return result_values
#
# def batch_translate(processed_texts, split_points,original_language,target_language):
# translated_texts = []
# index_mapping = {}
#
# start_index = 0
#
# for split_point in split_points:
# batch = processed_texts[start_index:split_point + 1]
# batch_texts = [text for text, _, _ in batch]
# translated_batch = translate(texts=batch_texts,original_language=original_language,target_language=target_language)
#
# for translated_text, (_, _, int_value) in zip(translated_batch, batch):
# if int_value in index_mapping:
# translated_texts[index_mapping[int_value]] += " " + translated_text
# else:
# index_mapping[int_value] = len(translated_texts)
# translated_texts.append(translated_text)
#
# start_index = split_point + 1
#
# return translated_texts
#
================================================
FILE: Bing_translation.py
================================================
import re
import requests
import time
import threading
import asyncio
import aiohttp
from concurrent.futures import ThreadPoolExecutor
def translate(texts, original_lang, target_lang):
"""
使用Bing翻译API翻译文本列表 - 高性能实现
Args:
texts: 要翻译的文本列表
original_lang: 源语言代码
target_lang: 目标语言代码
Returns:
翻译后的文本列表
"""
# 确保输入文本为列表格式
if isinstance(texts, str):
texts = [texts]
# 如果文本量小,使用简单的并发线程池
if len(texts) <= 20:
return translate_with_threadpool(texts, original_lang, target_lang)
# 对于大量文本,使用异步IO处理
return translate_with_asyncio(texts, original_lang, target_lang)
def translate_with_threadpool(texts, original_lang, target_lang, max_workers=5):
"""使用线程池并发翻译小批量文本"""
translator = BingTranslator(lang_in=original_lang, lang_out=target_lang)
translated_texts = [""] * len(texts)
def translate_one(index, text):
try:
translated_texts[index] = translator.do_translate(text)
except Exception as e:
print(f"翻译文本时出错 (索引 {index}): {e}")
translated_texts[index] = ""
# 使用线程池并发处理
with ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = [executor.submit(translate_one, i, text)
for i, text in enumerate(texts)]
# 等待所有任务完成
for future in futures:
future.result()
return translated_texts
def translate_with_asyncio(texts, original_lang, target_lang):
"""使用asyncio异步处理大批量文本"""
# 定义异步主函数
async def main():
translator = AsyncBingTranslator(lang_in=original_lang, lang_out=target_lang)
return await translator.translate_batch(texts)
# 如果当前线程没有事件循环,创建一个新的
try:
loop = asyncio.get_event_loop()
except RuntimeError:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
# 运行异步函数并返回结果
return loop.run_until_complete(main())
def split_text_intelligently(text, max_length=1000):
"""智能分段文本,尽量在句子边界处断开"""
if len(text) <= max_length:
return [text]
parts = []
start = 0
while start < len(text):
# 如果剩余文本不足max_length,直接添加
if len(text) - start <= max_length:
parts.append(text[start:])
break
# 计算当前段落的结束位置
end = start + max_length
# 尝试在句子结束处断开(优先级:段落 > 句号 > 逗号 > 空格)
paragraph_break = text.rfind('\n', start, end)
if paragraph_break != -1 and paragraph_break > start + max_length * 0.5:
end = paragraph_break + 1
else:
# 寻找句号、问号、感叹号等
for sep in ['. ', '。', '?', '!', '? ', '! ']:
pos = text.rfind(sep, start, end)
if pos != -1 and pos > start + max_length * 0.5:
end = pos + len(sep)
break
else:
# 如果没找到句号,尝试在逗号处断开
for sep in [', ', ',', '; ', ';']:
pos = text.rfind(sep, start, end)
if pos != -1 and pos > start + max_length * 0.7:
end = pos + len(sep)
break
else:
# 实在没有好的断点就在空格处断开
pos = text.rfind(' ', start + max_length * 0.8, end)
if pos != -1:
end = pos + 1
parts.append(text[start:end])
start = end
return parts
class BingTranslator:
name = "bing"
lang_map = {"zh": "zh-Hans"}
# 会话参数缓存
_cache_lock = threading.Lock()
_sid_cache = None
_sid_timestamp = 0
_sid_cache_ttl = 300 # 5分钟缓存有效期
def __init__(self, lang_in, lang_out, model=None, ignore_cache=False):
# 处理语言代码映射
self.lang_in = self.lang_map.get(lang_in, lang_in)
self.lang_out = self.lang_map.get(lang_out, lang_out)
# 自动语言检测处理
if self.lang_in == "auto":
self.lang_in = "auto-detect"
self.model = model
self.ignore_cache = ignore_cache
self.session = requests.Session()
self.endpoint = "https://www.bing.com/translator"
self.headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0",
}
def find_sid(self):
"""获取必要的会话参数,使用缓存减少请求"""
current_time = time.time()
# 检查缓存是否有效
with self._cache_lock:
if (not self.ignore_cache and
BingTranslator._sid_cache is not None and
(current_time - BingTranslator._sid_timestamp) < BingTranslator._sid_cache_ttl):
return BingTranslator._sid_cache
# 缓存无效,重新获取参数
response = self.session.get(self.endpoint, headers=self.headers)
response.raise_for_status()
url = response.url[:-10]
ig = re.findall(r"\"ig\":\"(.*?)\"", response.text)[0]
iid = re.findall(r"data-iid=\"(.*?)\"", response.text)[-1]
key, token = re.findall(
r"params_AbusePreventionHelper\s=\s\[(.*?),\"(.*?)\",", response.text
)[0]
# 更新缓存
result = (url, ig, iid, key, token)
with self._cache_lock:
BingTranslator._sid_cache = result
BingTranslator._sid_timestamp = current_time
return result
def do_translate(self, text):
"""执行翻译"""
if not text or not text.strip():
return ""
# 如果文本超过1000字符,分段翻译
if len(text) > 1000:
parts = split_text_intelligently(text)
translated_parts = []
for part in parts:
url, ig, iid, key, token = self.find_sid()
response = self.session.post(
f"{url}ttranslatev3?IG={ig}&IID={iid}",
data={
"fromLang": self.lang_in,
"to": self.lang_out,
"text": part[:1000], # 确保不超过1000
"token": token,
"key": key,
},
headers=self.headers,
)
response.raise_for_status()
translated_parts.append(response.json()[0]["translations"][0]["text"])
return ''.join(translated_parts)
url, ig, iid, key, token = self.find_sid()
response = self.session.post(
f"{url}ttranslatev3?IG={ig}&IID={iid}",
data={
"fromLang": self.lang_in,
"to": self.lang_out,
"text": text,
"token": token,
"key": key,
},
headers=self.headers,
)
response.raise_for_status()
return response.json()[0]["translations"][0]["text"]
class AsyncBingTranslator:
"""异步Bing翻译器实现"""
lang_map = {"zh": "zh-Hans"}
# 会话参数缓存
_sid_cache = None
_sid_timestamp = 0
_sid_cache_ttl = 300 # 5分钟缓存有效期
def __init__(self, lang_in, lang_out):
self.lang_in = self.lang_map.get(lang_in, lang_in)
self.lang_out = self.lang_map.get(lang_out, lang_out)
if self.lang_in == "auto":
self.lang_in = "auto-detect"
self.headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0",
}
self.endpoint = "https://www.bing.com/translator"
async def find_sid(self, session):
"""异步获取会话参数,带缓存"""
current_time = time.time()
# 检查缓存是否有效
if (AsyncBingTranslator._sid_cache is not None and
(current_time - AsyncBingTranslator._sid_timestamp) < AsyncBingTranslator._sid_cache_ttl):
return AsyncBingTranslator._sid_cache
# 缓存无效,异步获取新参数
async with session.get(self.endpoint, headers=self.headers) as response:
if response.status != 200:
raise Exception(f"获取会话参数失败: HTTP {response.status}")
text = await response.text()
url = str(response.url)[:-10]
ig = re.findall(r"\"ig\":\"(.*?)\"", text)[0]
iid = re.findall(r"data-iid=\"(.*?)\"", text)[-1]
key, token = re.findall(
r"params_AbusePreventionHelper\s=\s\[(.*?),\"(.*?)\",", text
)[0]
# 更新缓存
result = (url, ig, iid, key, token)
AsyncBingTranslator._sid_cache = result
AsyncBingTranslator._sid_timestamp = current_time
return result
async def translate_text(self, session, text):
"""翻译单个文本"""
if not text or not text.strip():
return ""
# 如果文本超过1000字符,分段翻译
if len(text) > 1000:
parts = split_text_intelligently(text)
translated_parts = []
# 非递归异步处理每个文本块
for part in parts:
url, ig, iid, key, token = await self.find_sid(session)
async with session.post(
f"{url}ttranslatev3?IG={ig}&IID={iid}",
data={
"fromLang": self.lang_in,
"to": self.lang_out,
"text": part[:1000], # 确保不超过1000
"token": token,
"key": key,
},
headers=self.headers,
) as response:
if response.status == 200:
result = await response.json()
translated_parts.append(result[0]["translations"][0]["text"])
else:
print(f"翻译请求失败: HTTP {response.status}")
translated_parts.append("")
return ''.join(translated_parts)
try:
url, ig, iid, key, token = await self.find_sid(session)
response = await session.post(
f"{url}ttranslatev3?IG={ig}&IID={iid}",
data={
"fromLang": self.lang_in,
"to": self.lang_out,
"text": text,
"token": token,
"key": key,
},
headers=self.headers,
)
if response.status == 200:
result = await response.json()
return result[0]["translations"][0]["text"]
else:
print(f"翻译请求失败: HTTP {response.status}")
return ""
except Exception as e:
print(f"翻译过程中发生错误: {e}")
print(f"原文: {text}")
return ""
async def translate_batch(self, texts, batch_size=10, max_concurrent=5):
"""批量翻译文本,控制并发数量和请求批次"""
async with aiohttp.ClientSession() as session:
results = [""] * len(texts)
semaphore = asyncio.Semaphore(max_concurrent)
async def translate_with_limit(index, text):
retry_count = 0
max_retries = 10
backoff_time = 1.0 # 初始重试等待时间
while retry_count < max_retries:
try:
async with semaphore:
# 每批次间隔较小的延迟
if index > 0 and index % batch_size == 0:
await asyncio.sleep(0.1)
translated = await self.translate_text(session, text)
if translated: # 如果翻译成功
results[index] = translated
if retry_count > 0: # 如果是重试成功的
print(f"第{index}个文本重试成功!")
return
except Exception as e:
print(f"第{index}个文本翻译失败 (尝试 {retry_count+1}/{max_retries}): {e}")
print(f"原文: {text}")
# 如果到这里,说明需要重试
retry_count += 1
if retry_count < max_retries:
print(f"将在{backoff_time}秒后重试...")
await asyncio.sleep(backoff_time)
backoff_time *= 2 # 指数退避策略
else:
print(f"已达到最大重试次数,翻译失败")
results[index] = ""
# 创建所有任务
tasks = [
asyncio.create_task(translate_with_limit(i, text))
for i, text in enumerate(texts)
]
# 等待所有任务完成
await asyncio.gather(*tasks)
return results
# 测试代码
if __name__ == "__main__":
test_texts = ["Hello, world!", "How are you today?", "Python is amazing", "I love programming"]
results = translate(test_texts, "en", "zh")
for original, translated in zip(test_texts, results):
print(f"Original: {original}")
print(f"Translated: {translated}")
print("-" * 30)
================================================
FILE: Deepl_Translation.py
================================================
import deepl
import load_config
def translate(texts,original_lang,target_lang):
# 你的 DeepL 授权密钥
# 获取指定服务的认证信息
config = load_config.load_config()
auth_key = config['translation_services']['deepl']['auth_key']
# print(auth_key)
translator = deepl.Translator(auth_key)
# 要翻译的文本列表
# 翻译文本列表,目标语言设置为中文
print(original_lang,target_lang)
if original_lang == 'auto':
results = translator.translate_text(texts, target_lang=target_lang)
else:
results = translator.translate_text(texts, source_lang=original_lang, target_lang=target_lang)
# 初始化一个空列表来收集翻译结果
translated_texts = []
# 遍历翻译结果,将它们添加到列表中
for result in results:
translated_texts.append(result.text)
return translated_texts
================================================
FILE: Dockerfile
================================================
# 1. 使用官方 Python 3.9 的精简版镜像作为基础
FROM python:3.9-slim
# 2. 如果你需要一些系统库支持,可在此处安装
# 比如安装 gcc、libssl-dev 等 (仅举例)
# RUN apt-get update && apt-get install -y --no-install-recommends \
# gcc \
# libssl-dev \
# && rm -rf /var/lib/apt/lists/*
# 3. 设置工作目录
WORKDIR /app
# 4. 将 requirements.txt 复制到容器内
COPY requirements.txt /app/
# 5. 安装 Python 依赖包
RUN pip install --no-cache-dir -r requirements.txt
# 6. 复制项目源代码到容器内
COPY . /app
# 7. 暴露端口 12226(如你的项目需要此端口)
EXPOSE 12226
# 8. 容器启动时,默认执行 Python 脚本
CMD ["python", "app.py"]
================================================
FILE: EbookTranslator/EbookTranslator/All_Translation.py
================================================
import time
import os
from .import Deepl_Translation as dt
from .import YouDao_translation as yt
from .import LLMS_translation as lt
import asyncio
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
# #
# Get the encoder of a specific model, assume gpt3.5, tiktoken is extremely fast,
# and the error of this statistical token method is small and can be ignored
class Online_translation:
def __init__(self, original_language, target_language, translation_type, texts_to_process=[]):
self.model_name = f"opus-mt-{original_language}-{target_language}"
self.original_text = texts_to_process
self.target_language = target_language
self.original_lang = original_language
self.translation_type = translation_type
def run_async(self, coro):
# 往往只要 run_until_complete(),不手动 close() 即可
return loop.run_until_complete(coro)
def translation(self):
print('translation api',self.translation_type)
if self.translation_type == 'deepl':
translated_list = self.deepl_translation()
elif self.translation_type == 'youdao':
translated_list = self.youdao_translation()
elif self.translation_type == 'bing':
# 使用同步包装器运行异步函数
translated_list = self.run_async(self.bing_translation())
elif self.translation_type == 'openai':
# 使用同步包装器运行异步函数
translated_list = self.run_async(self.openai_translation())
elif self.translation_type == 'deepseek':
# 使用同步包装器运行异步函数
translated_list = self.run_async(self.deepseek_translation())
elif self.translation_type == 'Doubao':
# 使用同步包装器运行异步函数
translated_list = self.run_async(self.Doubao_translation())
elif self.translation_type == 'Qwen':
# 使用同步包装器运行异步函数
translated_list = self.run_async(self.Qwen_translation())
elif self.translation_type == 'Grok':
# 使用同步包装器运行异步函数
translated_list = self.run_async(self.Grok_translation())
elif self.translation_type == 'ThirdParty':
# 使用同步包装器运行异步函数
translated_list = self.run_async(self.ThirdParty_translation())
elif self.translation_type == 'GLM':
# 使用同步包装器运行异步函数
translated_list = self.run_async(self.GLM_translation())
else:
translated_list = self.deepl_translation()
return translated_list
def deepl_translation(self):
translated_texts = dt.translate(texts=self.original_text,original_lang=self.original_lang,target_lang=self.target_language)
return translated_texts
def youdao_translation(self):
translated_texts = yt.translate(texts=self.original_text,original_lang=self.original_lang,target_lang=self.target_language)
return translated_texts
async def openai_translation(self):
translator = lt.Openai_translation()
translated_texts = await translator.translate(
texts=self.original_text,
original_lang=self.original_lang,
target_lang=self.target_language
)
return translated_texts
async def deepseek_translation(self):
translator = lt.Deepseek_translation()
translated_texts = await translator.translate(
texts=self.original_text,
original_lang=self.original_lang,
target_lang=self.target_language
)
return translated_texts
async def Doubao_translation(self):
translator = lt.Doubao_translation()
translated_texts = await translator.translate(
texts=self.original_text,
original_lang=self.original_lang,
target_lang=self.target_language
)
return translated_texts
async def Qwen_translation(self):
translator = lt.Qwen_translation()
translated_texts = await translator.translate(
texts=self.original_text,
original_lang=self.original_lang,
target_lang=self.target_language
)
return translated_texts
async def Grok_translation(self):
translator = lt.Grok_translation()
try:
translated_texts = await translator.translate(
texts=self.original_text,
original_lang=self.original_lang,
target_lang=self.target_language
)
print(f"Grok translation completed: {len(translated_texts)} texts processed")
return translated_texts
except Exception as e:
print(f"Error in Grok translation: {e}")
return [""] * len(self.original_text)
async def ThirdParty_translation(self):
translator = lt.ThirdParty_translation()
try:
translated_texts = await translator.translate(
texts=self.original_text,
original_lang=self.original_lang,
target_lang=self.target_language
)
print(f"ThirdParty translation completed: {len(translated_texts)} texts processed")
return translated_texts
except Exception as e:
print(f"Error in ThirdParty translation: {e}")
return [""] * len(self.original_text)
async def GLM_translation(self):
translator = lt.GLM_translation()
try:
translated_texts = await translator.translate(
texts=self.original_text,
original_lang=self.original_lang,
target_lang=self.target_language
)
print(f"GLM translation completed: {len(translated_texts)} texts processed")
return translated_texts
except Exception as e:
print(f"Error in GLM translation: {e}")
return [""] * len(self.original_text)
async def bing_translation(self):
translator = lt.Bing_translation()
try:
translated_texts = await translator.translate(
texts=self.original_text,
original_lang=self.original_lang,
target_lang=self.target_language
)
print(f"Bing translation completed: {len(translated_texts)} texts processed")
return translated_texts
except Exception as e:
print(f"Error in Bing translation: {e}")
return [""] * len(self.original_text)
t = time.time()
def split_text_to_fit_token_limit(text, encoder, index_text, max_length=280):
tokens = encoder.encode(text)
if len(tokens) <= max_length:
return [(text, len(tokens), index_text)] # Return text along with its token count and original index 返回文本及其标记计数和原始索引
# Pre-calculate possible split points (spaces, periods, etc.)
split_points = [i for i, token in enumerate(tokens) if encoder.decode([token]).strip() in [' ', '.', '?', '!','!','?','。']]
parts = []
last_split = 0
for i, point in enumerate(split_points + [len(tokens)]): # Ensure the last segment is included
if point - last_split > max_length:
part_tokens = tokens[last_split:split_points[i - 1]]
parts.append((encoder.decode(part_tokens), len(part_tokens), index_text))
last_split = split_points[i - 1]
elif i == len(split_points): # Handle the last part
part_tokens = tokens[last_split:]
parts.append((encoder.decode(part_tokens), len(part_tokens), index_text))
return parts
def process_texts(texts, encoder):
processed_texts = []
for i, text in enumerate(texts):
sub_texts = split_text_to_fit_token_limit(text, encoder, i)
processed_texts.extend(sub_texts)
return processed_texts
def calculate_split_points(processed_texts, max_tokens=425):
split_points = [] # 存储划分点的索引
current_tokens = 0 # 当前累积的token数
for i in range(len(processed_texts) - 1): # 遍历到倒数第二个元素
current_tokens = processed_texts[i][1]
next_tokens = processed_texts[i + 1][1]
# 如果当前元素和下一个元素的token数之和超过了限制
if current_tokens + next_tokens > max_tokens:
split_points.append(i) # 当前元素作为一个划分点
# 注意:这里不需要重置 current_tokens,因为每次循环都是新的一对元素
# 最后一个元素总是一个划分点,因为它后面没有元素与之相邻
split_points.append(len(processed_texts) - 1)
return split_points
def translate(texts,original_language,target_language):
# 这里仅返回相同的文本列表作为示例,实际中应返回翻译后的文本
from transformers import pipeline, AutoTokenizer
model_name = f"./opus-mt-{original_language}-{target_language}" # 请替换为实际路径
# 创建翻译管道,指定本地模型路径
pipe = pipeline("translation", model=model_name)
# 获取tokenizer,指定本地模型路径
tokenizer = AutoTokenizer.from_pretrained(model_name)
result = pipe(texts)
# 提取值并组合成新的列表
result_values = [d['translation_text'] for d in result]
return result_values
def batch_translate(processed_texts, split_points,original_language,target_language):
translated_texts = [] # 存储翻译后的文本的列表
index_mapping = {} # 存储每个int_value对应在translated_texts中的索引
start_index = 0 # 当前批次的起始索引
# 遍历划分点,按批次翻译文本
for split_point in split_points:
# 提取当前批次的文本(不包括划分点的下一个元素)
batch = processed_texts[start_index:split_point + 1]
batch_texts = [text for text, _, _ in batch]
# 翻译函数
translated_batch = translate(texts=batch_texts,original_language=original_language,target_language=target_language)
# 遍历当前批次的翻译结果
for translated_text, (_, _, int_value) in zip(translated_batch, batch):
if int_value in index_mapping:
# 如果键已存在,将新的翻译文本与原有的值拼接
translated_texts[index_mapping[int_value]] += " " + translated_text
else:
# 如果键不存在,直接添加到列表,并记录其索引
index_mapping[int_value] = len(translated_texts)
translated_texts.append(translated_text)
# 更新下一批次的起始索引
start_index = split_point + 1
return translated_texts
================================================
FILE: EbookTranslator/EbookTranslator/Deepl_Translation.py
================================================
import deepl
from .import load_config
def translate(texts,original_lang,target_lang):
# 你的 DeepL 授权密钥
# 获取指定服务的认证信息
config = load_config.load_config()
auth_key = config['translation_services']['deepl']['auth_key']
# print(auth_key)
translator = deepl.Translator(auth_key)
# 要翻译的文本列表
# 翻译文本列表,目标语言设置为中文
print(original_lang,target_lang)
if original_lang == 'auto':
results = translator.translate_text(texts, target_lang=target_lang)
else:
results = translator.translate_text(texts, source_lang=original_lang, target_lang=target_lang)
# 初始化一个空列表来收集翻译结果
translated_texts = []
# 遍历翻译结果,将它们添加到列表中
for result in results:
translated_texts.append(result.text)
return translated_texts
================================================
FILE: EbookTranslator/EbookTranslator/LLMS_translation.py
================================================
import asyncio
import aiohttp
import re
import requests
from . import load_config
class Openai_translation:
def __init__(self):
config = load_config.load_config()
self.api_key = config['translation_services']['openai']['auth_key']
self.url = "https://api.openai.com/v1/chat/completions"
self.headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
self.model = config['translation_services']['openai']['model_name']
async def translate_single(self, session, text, original_lang, target_lang):
"""单个文本的异步翻译"""
payload = {
"model": self.model,
"messages": [
{
"role": "system",
"content": f"You are a professional translator. Translate from {original_lang} to {target_lang}.Return only the translations"
},
{
"role": "user",
"content": text
}
],
"response_format": {
"type": "text"
},
"temperature": 0.3,
"top_p": 0.9
}
try:
async with session.post(self.url, headers=self.headers, json=payload) as response:
if response.status == 200:
result = await response.json()
return result['choices'][0]['message']['content'].strip()
else:
print(f"Error: {response.status}")
return ""
except Exception as e:
print(f"Error in translation: {e}")
return ""
async def translate(self, texts, original_lang, target_lang):
"""异步批量翻译"""
async with aiohttp.ClientSession() as session:
tasks = [
self.translate_single(session, text, original_lang, target_lang)
for text in texts
]
return await asyncio.gather(*tasks)
# 添加Bing翻译类
class Bing_translation:
def __init__(self):
self.session = requests.Session()
self.endpoint = "https://www.bing.com/translator"
self.headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0",
}
self.lang_map = {"zh": "zh-Hans"}
async def find_sid(self):
"""获取必要的会话参数"""
loop = asyncio.get_event_loop()
response = await loop.run_in_executor(None, lambda: self.session.get(self.endpoint, headers=self.headers))
response.raise_for_status()
url = response.url[:-10]
ig = re.findall(r"\"ig\":\"(.*?)\"", response.text)[0]
iid = re.findall(r"data-iid=\"(.*?)\"", response.text)[-1]
key, token = re.findall(
r"params_AbusePreventionHelper\s=\s\[(.*?),\"(.*?)\",", response.text
)[0]
return url, ig, iid, key, token
async def translate_single(self, session, text, original_lang, target_lang):
"""单个文本的异步翻译"""
if not text or not text.strip():
return ""
# 处理语言代码映射
lang_in = self.lang_map.get(original_lang, original_lang)
lang_out = self.lang_map.get(target_lang, target_lang)
# 自动语言检测处理
if lang_in == "auto":
lang_in = "auto-detect"
# Bing翻译最大长度限制
text = text[:1000]
try:
url, ig, iid, key, token = await self.find_sid()
# 通过异步HTTP请求执行翻译
async with session.post(
f"{url}ttranslatev3?IG={ig}&IID={iid}",
data={
"fromLang": lang_in,
"to": lang_out,
"text": text,
"token": token,
"key": key,
},
headers=self.headers,
) as response:
if response.status == 200:
result = await response.json()
return result[0]["translations"][0]["text"]
else:
error_text = await response.text()
print(f"Bing翻译错误: {response.status}, 详情: {error_text}")
return ""
except Exception as e:
print(f"Bing翻译过程中发生错误: {e}")
return ""
async def translate(self, texts, original_lang, target_lang):
"""异步批量翻译"""
print(f"开始Bing翻译,共 {len(texts)} 个文本")
async with aiohttp.ClientSession() as session:
tasks = []
for i, text in enumerate(texts):
# 添加延迟以避免请求过快被屏蔽
await asyncio.sleep(0.5 * (i % 3)) # 每3个请求一组,每组之间间隔0.5秒
tasks.append(self.translate_single(session, text, original_lang, target_lang))
results = await asyncio.gather(*tasks)
print(f"Bing翻译完成,共翻译 {len(results)} 个文本")
return results
class Deepseek_translation:
def __init__(self):
config = load_config.load_config()
self.api_key = config['translation_services']['deepseek']['auth_key']
self.url = "https://ark.cn-beijing.volces.com/api/v3/chat/completions"
self.headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
self.model = config['translation_services']['deepseek']['model_name']
async def translate_single(self, session, text, original_lang, target_lang):
"""单个文本的异步翻译"""
payload = {
"model": self.model,
"messages": [
{
"role": "system",
"content": f"You are a professional translator. Translate from {original_lang} to {target_lang}.Return only the translations"
},
{
"role": "user",
"content": text
}
],
"temperature": 0.3,
"top_p": 0.9
}
try:
async with session.post(self.url, headers=self.headers, json=payload) as response:
if response.status == 200:
result = await response.json()
return result['choices'][0]['message']['content'].strip()
else:
print(f"Error: {response.status}")
return ""
except Exception as e:
print(f"Error in translation: {e}")
return ""
async def translate(self, texts, original_lang, target_lang):
"""异步批量翻译"""
async with aiohttp.ClientSession() as session:
tasks = [
self.translate_single(session, text, original_lang, target_lang)
for text in texts
]
return await asyncio.gather(*tasks)
class Doubao_translation:
def __init__(self):
config = load_config.load_config()
self.api_key = config['translation_services']['Doubao']['auth_key']
self.url = "https://ark.cn-beijing.volces.com/api/v3/chat/completions"
self.headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
self.model = config['translation_services']['Doubao']['model_name']
async def translate_single(self, session, text, original_lang, target_lang):
"""单个文本的异步翻译"""
payload = {
"model": self.model,
"messages": [
{
"role": "system",
"content": f"Translate from {original_lang} to {target_lang}. Return ONLY the translation. No explanations. No notes. No annotations."
},
{
"role": "user",
"content": text
}
],
"temperature": 0.3,
"top_p": 0.9
}
try:
async with session.post(self.url, headers=self.headers, json=payload) as response:
if response.status == 200:
result = await response.json()
return result['choices'][0]['message']['content'].strip()
else:
print(f"Error: {response.status}")
return ""
except Exception as e:
print(f"Error in translation: {e}")
return ""
async def translate(self, texts, original_lang, target_lang):
"""异步批量翻译"""
async with aiohttp.ClientSession() as session:
tasks = [
self.translate_single(session, text, original_lang, target_lang)
for text in texts
]
return await asyncio.gather(*tasks)
class Qwen_translation:
def __init__(self):
config = load_config.load_config()
self.api_key = config['translation_services']['Qwen']['auth_key']
self.url = "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions"
self.headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
self.model = config['translation_services']['Qwen']['model_name']
async def translate_single(self, session, text, original_lang, target_lang):
"""单个文本的异步翻译"""
payload = {
"model": self.model,
"messages": [
{
"role": "system",
"content": f"You are a professional translator. Translate from {original_lang} to {target_lang}.Return only the translations"
},
{
"role": "user",
"content": text
}
],
"temperature": 0.3,
"top_p": 0.9
}
try:
async with session.post(self.url, headers=self.headers, json=payload) as response:
if response.status == 200:
result = await response.json()
return result['choices'][0]['message']['content'].strip()
else:
print(f"Error: {response.status}")
return ""
except Exception as e:
print(f"Error in translation: {e}")
return ""
async def translate(self, texts, original_lang, target_lang):
"""异步批量翻译"""
async with aiohttp.ClientSession() as session:
tasks = [
self.translate_single(session, text, original_lang, target_lang)
for text in texts
]
return await asyncio.gather(*tasks)
class Grok_translation:
def __init__(self):
config = load_config.load_config()
# 修改键名为大写的'Grok'以匹配其他API命名风格
self.api_key = config['translation_services']['Grok']['auth_key']
self.url = "https://api-proxy.me/xai/v1/chat/completions"
self.headers = {
"Content-Type": "application/json",
"X-Api-Key": self.api_key,
"Authorization": f"Bearer {self.api_key}"
}
self.model = config['translation_services']['Grok']['model_name']
async def translate_single(self, session, text, original_lang, target_lang):
"""单个文本的异步翻译"""
payload = {
"model": self.model,
"messages": [
{
"role": "system",
"content": f"You are a professional translator. Translate from {original_lang} to {target_lang}. Return ONLY the translation without explanations or notes."
},
{
"role": "user",
"content": text
}
],
"temperature": 0.3,
"stream": False
}
try:
async with session.post(self.url, headers=self.headers, json=payload) as response:
if response.status == 200:
result = await response.json()
return result['choices'][0]['message']['content'].strip()
else:
print(f"Error: {response.status}")
return ""
except Exception as e:
print(f"Error in translation: {e}")
return ""
async def translate(self, texts, original_lang, target_lang):
"""异步批量翻译"""
async with aiohttp.ClientSession() as session:
tasks = [
self.translate_single(session, text, original_lang, target_lang)
for text in texts
]
return await asyncio.gather(*tasks)
class ThirdParty_translation:
def __init__(self):
config = load_config.load_config()
self.api_key = config['translation_services']['ThirdParty']['auth_key']
self.url = config['translation_services']['ThirdParty']['api_url']
self.headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
self.model = config['translation_services']['ThirdParty']['model_name']
async def translate_single(self, session, text, original_lang, target_lang):
"""单个文本的异步翻译"""
payload = {
"model": self.model,
"messages": [
{
"role": "system",
"content": f"You are a professional translator. Translate from {original_lang} to {target_lang}. Return ONLY the translation without explanations or notes."
},
{
"role": "user",
"content": text
}
],
"temperature": 0.3,
"stream": False
}
try:
async with session.post(self.url, headers=self.headers, json=payload) as response:
if response.status == 200:
result = await response.json()
translated_text = result['choices'][0]['message']['content'].strip()
# 添加调试日志
print(f"ThirdParty translated: '{text[:30]}...' -> '{translated_text[:30]}...'")
return translated_text
else:
error_text = await response.text()
print(f"Error: {response.status}, Details: {error_text}")
return ""
except Exception as e:
print(f"Error in translation: {e}")
return ""
async def translate(self, texts, original_lang, target_lang):
"""异步批量翻译"""
print(f"Starting ThirdParty translation of {len(texts)} texts")
async with aiohttp.ClientSession() as session:
tasks = [
self.translate_single(session, text, original_lang, target_lang)
for text in texts
]
results = await asyncio.gather(*tasks)
print(f"ThirdParty translation completed, {len(results)} texts translated")
return results
class GLM_translation:
def __init__(self):
config = load_config.load_config()
self.api_key = config['translation_services']['GLM']['auth_key']
self.url = "https://open.bigmodel.cn/api/paas/v4/chat/completions"
self.headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
self.model = config['translation_services']['GLM']['model_name']
async def translate_single(self, session, text, original_lang, target_lang):
"""单个文本的异步翻译"""
payload = {
"model": self.model,
"messages": [
{
"role": "system",
"content": f"You are a professional translator. Translate from {original_lang} to {target_lang}. Return ONLY the translation without explanations or notes."
},
{
"role": "user",
"content": text
}
],
"temperature": 0.3,
"top_p": 0.7,
"do_sample": False
}
try:
async with session.post(self.url, headers=self.headers, json=payload) as response:
if response.status == 200:
result = await response.json()
return result['choices'][0]['message']['content'].strip()
else:
error_text = await response.text()
print(f"Error: {response.status}, Details: {error_text}")
return ""
except Exception as e:
print(f"Error in GLM translation: {e}")
return ""
async def translate(self, texts, original_lang, target_lang):
"""异步批量翻译"""
print(f"Starting GLM translation of {len(texts)} texts")
async with aiohttp.ClientSession() as session:
tasks = [
self.translate_single(session, text, original_lang, target_lang)
for text in texts
]
results = await asyncio.gather(*tasks)
print(f"GLM translation completed, {len(results)} texts translated")
return results
# 测试代码
async def main():
texts = [
"Hello, how are you?",
"What's the weather like today?",
"I love programming",
"Python is awesome",
"Machine learning is interesting"
]
# OpenAI翻译测试
openai_translator = Openai_translation("gpt-3.5-turbo")
translated_openai = await openai_translator.translate(
texts=texts,
original_lang="en",
target_lang="zh"
)
print("OpenAI translations:")
for src, tgt in zip(texts, translated_openai):
print(f"{src} -> {tgt}")
# DeepSeek翻译测试
deepseek_translator = Deepseek_translation()
translated_deepseek = await deepseek_translator.translate(
texts=texts,
original_lang="en",
target_lang="zh"
)
print("\nDeepSeek translations:")
for src, tgt in zip(texts, translated_deepseek):
print(f"{src} -> {tgt}")
qwen_translator = Qwen_translation()
translated_deepseek = await qwen_translator.translate(
texts=texts,
original_lang="en",
target_lang="zh"
)
print("\nQwen translations:")
for src, tgt in zip(texts, translated_deepseek):
print(f"{src} -> {tgt}")
# 添加Grok翻译测试
grok_translator = Grok_translation()
translated_grok = await grok_translator.translate(
texts=texts,
original_lang="en",
target_lang="zh"
)
print("\nGrok translations:")
for src, tgt in zip(texts, translated_grok):
print(f"{src} -> {tgt}")
# 添加ThirdParty翻译测试
try:
thirdparty_translator = ThirdParty_translation()
translated_thirdparty = await thirdparty_translator.translate(
texts=texts,
original_lang="en",
target_lang="zh"
)
print("\nThirdParty translations:")
for src, tgt in zip(texts, translated_thirdparty):
print(f"{src} -> {tgt}")
except Exception as e:
print(f"Error testing ThirdParty translation: {e}")
# 添加 GLM 翻译测试
try:
glm_translator = GLM_translation()
translated_glm = await glm_translator.translate(
texts=texts,
original_lang="en",
target_lang="zh"
)
print("\nGLM translations:")
for src, tgt in zip(texts, translated_glm):
print(f"{src} -> {tgt}")
except Exception as e:
print(f"Error testing GLM translation: {e}")
if __name__ == "__main__":
asyncio.run(main())
================================================
FILE: EbookTranslator/EbookTranslator/YouDao_translation.py
================================================
import uuid
import requests
import hashlib
import time
import json
def translate(texts,original_lang, target_lang):
"""
有道翻译API接口
参数:
texts: list, 要翻译的文本列表
target_lang: str, 目标语言代码
credentials: dict, 包含 app_key 和 app_secret 的字典
返回:
list: 翻译后的文本列表
"""
YOUDAO_URL = 'https://openapi.youdao.com/v2/api'
with open("config.json", 'r', encoding='utf-8') as f:
config = json.load(f)
# 获取指定服务的认证信息
if target_lang == 'zh':
target_lang='zh-CHS'
service_name = "youdao"
credentials = config['translation_services'].get(service_name)
if not credentials:
raise ValueError(f"Translation service '{service_name}' not found in config")
def encrypt(sign_str):
hash_algorithm = hashlib.sha256()
hash_algorithm.update(sign_str.encode('utf-8'))
return hash_algorithm.hexdigest()
def truncate(q):
if q is None:
return None
size = len(q)
return q if size <= 20 else q[0:10] + str(size) + q[size - 10:size]
def do_request(data):
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
return requests.post(YOUDAO_URL, data=data, headers=headers)
try:
# 确保输入文本为列表格式
if isinstance(texts, str):
texts = [texts]
print(type(texts))
# 准备请求数据
data = {
'from': original_lang,
'to': target_lang,
'signType': 'v3',
'curtime': str(int(time.time())),
'appKey': credentials['app_key'],
'q': texts,
'salt': str(uuid.uuid1()),
'vocabId': "您的用户词表ID"
}
# 生成签名
sign_str = (credentials['app_key'] +
truncate(''.join(texts)) +
data['salt'] +
data['curtime'] +
credentials['app_secret'])
data['sign'] = encrypt(sign_str)
# 发送请求
response = do_request(data)
response_data = json.loads(response.content.decode("utf-8"))
# 提取翻译结果
translations = [result["translation"] for result in response_data["translateResults"]]
print(translations)
return translations
except Exception as e:
print(f"翻译出错: {str(e)}")
return None
# 使用示例:
if __name__ == '__main__':
# 认证信息
# 要翻译的文本
texts = ["hello", '待输入的文字"2', "待输入的文字3"]
original_lang = 'auto'
# 目标语言
target_lang = 'zh'
# 调用翻译
results = translate(texts,original_lang='auto', target_lang=target_lang)
print(results,'ggg')
if results:
for original, translated in zip(texts, results):
print(f"原文: {original}")
print(f"译文: {translated}\n")
================================================
FILE: EbookTranslator/EbookTranslator/__init__.py
================================================
"""
EbookTranslator - 世界上性能最高的电子书保留布局翻译库
The world's highest performing e-book retention layout translation library
"""
__version__ = '0.1.0'
from .main_function import main_function
# 导出主要类和函数
__all__ = ['main_function']
================================================
FILE: EbookTranslator/EbookTranslator/cli.py
================================================
#!/usr/bin/env python3
"""
EbookTranslator的命令行界面
"""
import argparse
import sys
import os
from pathlib import Path
from .main_function import main_function
def main():
"""命令行入口点"""
parser = argparse.ArgumentParser(description='翻译PDF文档')
parser.add_argument('pdf_path', type=str, help='PDF文件路径')
parser.add_argument('-o', '--original', default='auto', help='原始语言 (默认: auto)')
parser.add_argument('-t', '--target', default='zh', help='目标语言 (默认: zh)')
parser.add_argument('-b', '--begin', type=int, default=1, help='开始页码 (默认: 1)')
parser.add_argument('-e', '--end', type=int, default=None, help='结束页码 (默认: 最后一页)')
parser.add_argument('-c', '--config', type=str, default=None, help='配置文件路径')
parser.add_argument('-d', '--dpi', type=int, default=72, help='OCR模式的DPI (默认: 72)')
args = parser.parse_args()
# 检查PDF文件是否存在
print('路径',args.pdf_path)
if not os.path.exists(args.pdf_path):
print(f"错误: 找不到文件 '{args.pdf_path}'")
sys.exit(1)
try:
# 运行主函数
translator = main_function(
pdf_path=args.pdf_path,
original_language=args.original,
target_language=args.target,
bn=args.begin,
en=args.end,
config_path=args.config,
DPI=args.dpi
)
translator.main()
print(f"翻译完成! 输出文件保存在 target 目录")
except Exception as e:
print(f"翻译过程中发生错误: {e}")
sys.exit(1)
if __name__ == '__main__':
main()
================================================
FILE: EbookTranslator/EbookTranslator/convert2pdf.py
================================================
import fitz
import os
def convert_to_pdf(input_file, output_file=None):
"""
将支持的文档格式转换为 PDF,支持跨平台路径处理
Args:
input_file (str): 输入文件的完整路径
output_file (str, optional): 输出PDF文件的完整路径。如果为None,则使用输入文件名+.pdf
Returns:
bool: 转换是否成功
"""
try:
# 规范化路径,处理不同平台的路径分隔符
input_file = os.path.normpath(input_file)
if not os.path.exists(input_file):
print(f"错误:输入文件 '{input_file}' 不存在")
return False
# 如果未指定输出文件,则基于输入文件生成输出路径
if output_file is None:
# 获取文件名和目录
file_dir = os.path.dirname(input_file)
file_name = os.path.basename(input_file)
name_without_ext = os.path.splitext(file_name)[0]
# 在同一目录下创建同名PDF文件
output_file = os.path.join(file_dir, f"{name_without_ext}.pdf")
# 确保输出目录存在
output_dir = os.path.dirname(output_file)
if output_dir and not os.path.exists(output_dir):
os.makedirs(output_dir, exist_ok=True)
print(f"正在处理文件: {input_file}")
print(f"输出文件将保存为: {output_file}")
# 1. 先用 fitz.open 打开文档(EPUB、XPS、FB2 等格式)
doc = fitz.open(input_file)
print(f"文档页数: {len(doc)}")
# 2. 调用 convert_to_pdf() 得到 PDF 格式字节流
pdf_bytes = doc.convert_to_pdf()
# 3. 再以 "pdf" 格式打开这段字节流
pdf_doc = fitz.open("pdf", pdf_bytes)
# 4. 保存为真正的 PDF 文件
pdf_doc.save(output_file)
# 关闭文档
pdf_doc.close()
doc.close()
# 检查输出文件是否成功创建
if os.path.exists(output_file):
print(f"转换成功!PDF文件已保存为: {output_file}")
return True
else:
print("转换似乎完成,但输出文件未找到")
return False
except fitz.FileDataError as e:
print(f"文件格式错误或文件损坏:{str(e)}")
except PermissionError as e:
print(f"权限错误:无法访问或写入文件 - {str(e)}")
except Exception as e:
print(f"转换失败,错误类型: {type(e).__name__}")
print(f"错误详情: {str(e)}")
# 在调试模式下打印完整的堆栈跟踪
import traceback
traceback.print_exc()
return False
# 使用示例
if __name__ == "__main__":
# 单个文件转换示例
input_file = "666 (1).epub"
# 验证文件扩展名
if not input_file.lower().endswith(('.xps', '.epub', '.fb2', '.cbz', '.mobi')):
print(f"不支持的文件格式。支持的格式包括: XPS, EPUB, FB2, CBZ, MOBI")
else:
convert_to_pdf(input_file)
# 批量转换示例
# input_directory = "documents"
# batch_convert_to_pdf(input_directory)
================================================
FILE: EbookTranslator/EbookTranslator/load_config.py
================================================
import os
import json
import requests
from pathlib import Path
from typing import Optional, Dict
def get_working_dir() -> Path:
"""
获取工作目录
返回当前工作目录(即命令行执行目录或调用脚本所在目录)
"""
return Path.cwd()
# 定义应用数据目录
WORKING_DIR = get_working_dir()
APP_DATA_DIR = WORKING_DIR # 示例:将 APP_DATA_DIR 定义为工作目录
print(f"Working directory: {WORKING_DIR}")
def resolve_path(path: str) -> Path:
"""
解析路径,支持绝对路径、相对路径和文件名。
Args:
path (str): 输入路径,可以是绝对路径、相对路径或文件名。
Returns:
Path: 解析后的完整路径。
"""
# 如果 path 是绝对路径,直接返回
if Path(path).is_absolute():
return Path(path)
# 如果 path 是相对路径或文件名,与 APP_DATA_DIR 拼接
return APP_DATA_DIR / path
def load_config(config_path: Optional[str] = None) -> Optional[Dict]:
"""
加载主配置文件,优先使用传入的 config_path 路径。
如果未传入或路径无效,则尝试使用 APP_DATA_DIR 中的文件。
如果 APP_DATA_DIR 中也没有 config.json,则从指定 URL 下载。
Args:
config_path (Optional[str]): 配置文件路径,可以是绝对路径、相对路径或文件名。
Returns:
Dict: 配置数据,如果加载失败则返回 None。
"""
try:
# 如果传入了 config_path 参数,优先使用
if config_path:
config_path = resolve_path(config_path) # 解析路径
if config_path.exists():
with config_path.open("r", encoding="utf-8") as f:
return json.load(f)
else:
print(f"Specified config path does not exist: {config_path}")
# 如果没有传入 config_path 或路径无效,则使用 APP_DATA_DIR 中的 config.json
app_config_path = APP_DATA_DIR / "config.json"
if app_config_path.exists():
with app_config_path.open("r", encoding="utf-8") as f:
return json.load(f)
else:
# 如果 APP_DATA_DIR 中没有,则尝试从指定 URL 下载 config.json
url = "https://raw.githubusercontent.com/CBIhalsen/PolyglotPDF/refs/heads/main/config.json"
response = requests.get(url, timeout=20)
if response.status_code == 200:
# 将下载的内容保存到 APP_DATA_DIR
APP_DATA_DIR.mkdir(parents=True, exist_ok=True) # 确保 APP_DATA_DIR 存在
print(
f"config.json file not found, downloading config.json from: {url}"
)
with app_config_path.open("w", encoding="utf-8") as f:
f.write(response.text)
return response.json()
else:
print(f"Failed to download config.json, HTTP status code: {response.status_code}")
return None
except Exception as e:
print(f"Error loading config: {str(e)}")
return None
def get_file_path(filename: str) -> Path:
"""
获取配置文件的完整路径,优先使用 APP_DATA_DIR 中的文件。
Args:
filename (str): 配置文件名。
Returns:
Path: 配置文件的完整路径。
"""
# 首先检查 APP_DATA_DIR 中是否有该文件
app_data_file = APP_DATA_DIR / filename
if app_data_file.exists():
return app_data_file
# 如果 APP_DATA_DIR 中没有,则使用当前脚本所在目录的文件
return Path(__file__).parent / filename
================================================
FILE: EbookTranslator/EbookTranslator/main_function.py
================================================
import math
from . import All_Translation as at
from PIL import Image
import pytesseract
import time
import fitz
import os
import unicodedata
from pathlib import Path
from . import load_config
import re
from .convert2pdf import convert_to_pdf
from .load_config import APP_DATA_DIR
# print(use_mupdf,'mupdf值')
# print('当前',config['count'])
def get_font_by_language(target_language):
font_mapping = {
'zh': "'Microsoft YaHei', 'SimSun'", # 中文
'en': "'Times New Roman', Arial", # 英文
'ja': "'MS Mincho', 'Yu Mincho'", # 日文
'ko': "'Malgun Gothic'", # 韩文
}
# 如果找不到对应语言,返回默认字体
return font_mapping.get(target_language, "'Times New Roman', Arial")
def is_math(text,pag_num,font_info):
"""
判断文本是否为非文本(如数学公式或者长度小于4的文本)
"""
# 判断文本长度
# print('文本为:',text)
text_len = len(text)
if text_len < 4:
return True
math_fonts = [
# Computer Modern Math
'CMMI', 'CMSY', 'CMEX',
'CMMI5', 'CMMI6', 'CMMI7', 'CMMI8', 'CMMI9', 'CMMI10',
'CMSY5', 'CMSY6', 'CMSY7', 'CMSY8', 'CMSY9', 'CMSY10',
# AMS Math
'MSAM', 'MSBM', 'EUFM', 'EUSM',
# Times/Palatino Math
'TXMI', 'TXSY', 'PXMI', 'PXSY',
# Modern Math
'CambriaMath', 'AsanaMath', 'STIXMath', 'XitsMath',
'Latin Modern Math', 'Neo Euler'
]
# 检查文本长度是否小于50且字体是否在数学字体列表中
text_len_non_sp = len(text.replace(" ", ""))
if text_len < 70 and any(math_font in font_info for math_font in math_fonts):
# print(text,'小于50且字体')
return True
if 15 < text_len_non_sp <100:
# 使用正则表达式找出所有5个或更多任意字符连续组成的单词
long_words = re.findall(r'\S{5,}', text)
if len(long_words) < 2:
# print(text_len)
# print(text, '15 < text_len <100')
return True
# 分行处理
lines = text.split('\n')
len_lines = len([line for line in lines if line.strip()])
# 找到长度最小和最大的行
min_line_len = min((len(line) for line in lines if line.strip()), default=text_len)
max_line_len = max((len(line) for line in lines), default=text_len)
# 计算空格比例
newline_count = text.count('\n')
total_spaces = text.count(' ') + (newline_count * 5)
space_ratio = total_spaces / text_len if text_len > 0 else 0
# 定义数学符号集合
math_symbols = "=∑θ∫∂√±ΣΠfδλσε∋∈µ→()|−ˆ,.+*/[]{}^_<>~#%&@!?;:'\"\\-"
# 检查是否存在完整单词(5个或更多非数学符号的连续字符)
text_no_spaces = text.replace(" ", "")
# 创建一个正则表达式,匹配5个或更多连续的非数学符号字符
pattern = r'[^' + re.escape(math_symbols) + r']{5,}'
has_complete_word = bool(re.search(pattern, text_no_spaces))
# 如果没有完整单词,认为是非文本
if not has_complete_word:
# print(text, '没有完整单词')
return True
# 计算数字占比
digit_count = sum(c.isdigit() for c in text)
digit_ratio = digit_count / text_len if text_len > 0 else 0
# 如果数字占比超过30%,返回True
if digit_ratio > 0.3:
# print(text, '数字占比超过30%')
return True
# 检查数学公式
math_symbols = set("=∑θ∫∂√±ΣΠδλσε∋∈µ→()|−ˆ,...")
# 数学公式判断条件2:包含至少2个数学符号且总文本较短
if sum(1 for sym in math_symbols if sym in text) >= 2 and len(text_no_spaces) < 25:
# found_symbols = [sym for sym in text if sym in math_symbols]
# print(f"在文本中找到的数学符号: {found_symbols}")
# print(sum(1 for sym in math_symbols if sym in text))
# print(text, '条件2')
return True
# 数学公式判断条件1:包含至少2个数学符号且行短且行数少且最大行长度小
if sum(1 for sym in math_symbols if sym in text) >= 2 and min_line_len < 10 and len_lines < 5 and max_line_len < 35:
# print(text, '条件1')
return True
# 数学公式判断条件3:包含至少2个数学符号且空格比例高
if sum(1 for sym in math_symbols if sym in text) >= 2 and space_ratio > 0.5:
# print(text, '条件3')
return True
# 数学公式判断条件4:包含至少1个数学符号且空格数高
if sum(1 for sym in math_symbols if sym in text) >= 1 and total_spaces > 3 and text_len<20:
# print(text, '条件4')
return True
return False
def line_non_text(text):
"""
判断文本是否由纯数字和所有(Unicode)标点符号组成
参数:
text: 待检查的文本
返回:
bool: 如果文本由纯数字和标点符号组成返回True,否则返回False
"""
text = text.strip()
if not text:
return False
for ch in text:
# 使用 unicodedata.category() 获取字符在 Unicode 标准中的分类
# 'Nd' 代表十进制数字 (Number, Decimal digit)
# 'P' 代表各种标点 (Punctuation),如 Po, Ps, Pe, 等
cat = unicodedata.category(ch)
if not (cat == 'Nd' or cat.startswith('P')):
return False
return True
def is_non_text(text):
"""
判断是否为参考文献格式
参数:
text: 待检查的文本
返回:
bool: 如果是参考文献格式返回True,否则返回False
"""
# 去除开头的空白字符
text = text.lstrip()
# 检查是否以[数字]开头
pattern = r'^\[\d+\]'
if re.match(pattern, text):
return True
return False
font_collection = []
class main_function:
def __init__(self, pdf_path,
original_language, target_language,bn = None,en = None,config_path = None,
DPI=72,):
"""
这里的参数与原来保持一致或自定义。主要多加一个 self.pages_data 用于存储所有页面的提取结果。
"""
self.config = self.get_config(config_path)
config = self.config
self.translation_type = config['default_services']['Translation_api']
self.translation = config['default_services']['Enable_translation']
self.use_mupdf = not config['default_services']['ocr_model']
self.PPC = config['PPC']
print('ppc',self.PPC)
self.line_model = config['default_services']['line_model']
print('line', self.line_model)
self.pdf_path = pdf_path
self.full_path = self.resolve_path(pdf_path)
self.doc = fitz.open(self.full_path)
self.original_language = original_language
self.target_language = target_language
self.DPI = DPI
self.bn = bn-1
self.en = en-1
self.t = time.time()
self.pdf_basename = os.path.basename(self.pdf_path) # g2.pdf
pdf_name_only, _ = os.path.splitext(self.pdf_basename) # g2
self.pdf_name = pdf_name_only
# 新增一个全局列表,用于存所有页面的 [文本, bbox],以及翻译后结果
# 形式: self.pages_data[page_index] = [ [原文, bbox], [原文, bbox], ... ]
self.pages_data = []
@staticmethod
def resolve_path(pdf_path: str) -> str:
"""
解析 PDF 文件路径,支持绝对路径、相对路径和文件名。
Args:
pdf_path (str): 输入路径,可以是绝对路径、相对路径或文件名。
Returns:
str: 解析后的绝对路径。
"""
path = Path(pdf_path)
# 如果是绝对路径,直接返回
if path.is_absolute():
return str(path)
# 如果是相对路径或文件名,与 APP_DATA_DIR 拼接
resolved_path = APP_DATA_DIR / path
# 返回解析后的绝对路径
return str(resolved_path.resolve())
def get_config(self,config_path=None):
config = load_config.load_config(config_path)
return config
def main(self):
"""
主流程函数。只做“计数更新、生成缩略图、建条目”等老逻辑,替换原来在这里的逐页翻译写入。
但是保留 if use_mupdf: for... self.start(...) else: for... self.start(...)
不做“翻译和写入”的动作,而是只做“提取文本”。
提取完所有页面后,批量翻译,再统一写入 PDF。
"""
file_extension = os.path.splitext(self.pdf_basename)[1].lower()
# 使用APP_DATA_DIR构建上传目录路径
# 如果不是PDF,进行转换
if file_extension != '.pdf':
# 创建PDF文件名
pdf_filename = os.path.splitext(self.pdf_basename)[0] + '.pdf'
pdf_file_path = os.path.join(APP_DATA_DIR, pdf_filename)
# 转换文件
if convert_to_pdf(input_file=self.full_path, output_file=pdf_file_path):
# 转换成功后删除原始文件
print('convert success')
# 4. 保留原先判断是否 use_mupdf 的代码,以便先提取文本
page_count =self.doc.page_count
if self.bn == None or self.bn <0:
self.bn = 0
if self.en == None or self.en <0:
self.en = page_count
if self.en < self.bn:
self.bn, self.en = self.en, self.bn
if self.use_mupdf:
start_page = self.bn
end_page = min(self.en, page_count)
# 使用 PyMuPDF 直接获取文本块
for i in range(start_page, end_page):
self.start(image=None, pag_num=i) # 只做提取,不做翻译写入
else:
# OCR 模式
zoom = self.DPI / 72
mat = fitz.Matrix(zoom, zoom)
# 处理从 self.bn 到 self.en 的页面范围,并确保 self.en 不超过文档页数
start_page = self.bn
end_page = min(self.en, page_count)
# 迭代指定范围的页面
for i in range(start_page, end_page):
page = self.doc[i] # 获取指定页面
pix = page.get_pixmap(matrix=mat)
image = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
# 如果需要保存图像到文件,可自行保留或注释
# image.save(f'page_{i}.jpg', 'JPEG')
self.start(image=image, pag_num=i) # 只做提取,不做翻译写入
# 5. 若开启翻译,则批量翻译所有提取的文本
self.batch_translate_pages_data(
original_language=self.original_language,
target_language=self.target_language,
translation_type=self.translation_type,
batch_size=self.PPC
)
# 6. 将翻译结果统一写入 PDF(覆盖+插入译文)
self.apply_translations_to_pdf()
# 7. 保存 PDF、更新状态
# 定义目标文件夹路径
target_folder = Path(APP_DATA_DIR) / "target"
# 如果目标文件夹不存在,则创建
target_folder.mkdir(parents=True, exist_ok=True)
# 定义目标 PDF 文件路径
target_path = target_folder / f"{self.pdf_name}_{self.target_language}.pdf"
print('save path ',target_path)
self.doc.ez_save(
target_path,
garbage=4,
deflate=True
)
# 8. 打印耗时
end_time = time.time()
print(end_time - self.t)
def start(self, image, pag_num):
"""
原先逐页处理的函数,现仅负责“提取文本并存储在 self.pages_data[pag_num]”。
不在这里直接翻译或写回 PDF。
"""
# 确保 self.pages_data 有 pag_num 对应的列表
while len(self.pages_data) <= pag_num:
self.pages_data.append([]) # 每个元素是 [ [text, (x0,y0,x1,y1)], ... ]
page = self.doc.load_page(pag_num)
if self.line_model and self.use_mupdf:
def snap_angle_func(angle):
"""
将任意角度自动映射到 0、90、180、270 四个值之一。
"""
# 将角度映射到 [0, 360) 区间
angle = abs(angle) % 360
# 选取最接近的标准角度
possible_angles = [0, 90, 180, 270]
return min(possible_angles, key=lambda x: abs(x - angle))
blocks = page.get_text("dict")["blocks"]
for block in blocks:
if block.get("type") == 0: # 文本块
font_info = None
# 遍历每一行
for line in block["lines"]:
# 1) 拼接文本(减少反复 += 操作)
span_texts = [span["text"] for span in line["spans"] if "text" in span]
line_text = "".join(span_texts).strip()
# 2) 如果行文本为空或仅含数字标点,就跳过
if not line_text or line_non_text(line_text):
continue
# 3) 此时才计算旋转角度,避免空行浪费
direction = line.get("dir", [1.0, 0.0])
raw_angle = math.degrees(math.atan2(direction[1], direction[0]))
angle = snap_angle_func(raw_angle)
# 4) 只在需要时提取字体信息
if not font_info:
for span in line["spans"]:
if "font" in span:
font_info = span["font"]
break
if font_info and font_info not in font_collection:
font_collection.append(font_info)
line_bbox = line.get("bbox")
# 5) 加入提取结果
self.pages_data[pag_num].append([
line_text, # 原文
tuple(line_bbox), # BBox
None, # 预留翻译文本
angle # 行角度
])
# 如果用 PyMuPDF 提取文字
elif self.use_mupdf and image is None:
blocks = page.get_text("dict")["blocks"]
for block in blocks:
if block.get("type") == 0: # 文本块
bbox = block["bbox"]
text = ""
font_info = None
for line in block["lines"]:
for span in line["spans"]:
span_text = span["text"].strip()
if span_text:
text += span_text + " "
if not font_info and "font" in span:
font_info = span["font"]
text = text.strip()
if text and not is_math(text, pag_num, font_info) and not is_non_text(text):
self.pages_data[pag_num].append([text, tuple(bbox),None])
else:
# OCR 提取文字
config = self.config
tesseract_path = config['ocr_services']['tesseract']['path']
pytesseract.pytesseract.tesseract_cmd = tesseract_path
Full_width, Full_height = image.size
ocr_result = pytesseract.image_to_data(image, output_type=pytesseract.Output.DICT)
current_paragraph_text = ''
paragraph_bbox = {
'left': float('inf'),
'top': float('inf'),
'right': 0,
'bottom': 0
}
current_block_num = None
Threshold_width = 0.06 * Full_width
Threshold_height = 0.006 * Full_height
for i in range(len(ocr_result['text'])):
block_num = ocr_result['block_num'][i]
text_ocr = ocr_result['text'][i].strip()
left = ocr_result['left'][i]
top = ocr_result['top'][i]
width = ocr_result['width'][i]
height = ocr_result['height'][i]
if text_ocr and not is_math(text_ocr, pag_num, font_info='22') and not is_non_text(text_ocr):
# 若换 block 或段落间隔较大,则保存上一段
if (block_num != current_block_num or
(abs(left - paragraph_bbox['right']) > Threshold_width and
abs(height - (paragraph_bbox['bottom'] - paragraph_bbox['top'])) > Threshold_height and
abs(left - paragraph_bbox['left']) > Threshold_width)):
if current_paragraph_text:
# 转换到 PDF 坐标
Full_rect = page.rect
w_points = Full_rect.width
h_points = Full_rect.height
x0_ratio = paragraph_bbox['left'] / Full_width
y0_ratio = paragraph_bbox['top'] / Full_height
x1_ratio = paragraph_bbox['right'] / Full_width
y1_ratio = paragraph_bbox['bottom'] / Full_height
x0_pdf = x0_ratio * w_points
y0_pdf = y0_ratio * h_points
x1_pdf = x1_ratio * w_points
y1_pdf = y1_ratio * h_points
self.pages_data[pag_num].append([
current_paragraph_text.strip(),
(x0_pdf, y0_pdf, x1_pdf, y1_pdf)
])
# 重置
current_paragraph_text = ''
paragraph_bbox = {
'left': float('inf'),
'top': float('inf'),
'right': 0,
'bottom': 0
}
current_block_num = block_num
# 继续累加文本
current_paragraph_text += text_ocr + " "
paragraph_bbox['left'] = min(paragraph_bbox['left'], left)
paragraph_bbox['top'] = min(paragraph_bbox['top'], top)
paragraph_bbox['right'] = max(paragraph_bbox['right'], left + width)
paragraph_bbox['bottom'] = max(paragraph_bbox['bottom'], top + height)
# 收尾:最后一段存入
if current_paragraph_text:
Full_rect = page.rect
w_points = Full_rect.width
h_points = Full_rect.height
x0_ratio = paragraph_bbox['left'] / Full_width
y0_ratio = paragraph_bbox['top'] / Full_height
x1_ratio = paragraph_bbox['right'] / Full_width
y1_ratio = paragraph_bbox['bottom'] / Full_height
x0_pdf = x0_ratio * w_points
y0_pdf = y0_ratio * h_points
x1_pdf = x1_ratio * w_points
y1_pdf = y1_ratio * h_points
self.pages_data[pag_num].append([
current_paragraph_text.strip(),
(x0_pdf, y0_pdf, x1_pdf, y1_pdf),
None
])
# 注意:这里不做翻译、不插入 PDF,只负责“收集文本”到 self.pages_data
def batch_translate_pages_data(self, original_language, target_language,
translation_type, batch_size=None ):
"""PPC (Pages Per Call)
分批翻译 pages_data,每次处理最多 batch_size 页的文本,避免一次性过多。
将译文存回 self.pages_data 的第三个元素,如 [原文, bbox, 译文]
"""
total_pages = len(self.pages_data)
start_idx = 0
while start_idx < total_pages:
end_idx = min(start_idx + batch_size, total_pages)
# 收集该批次的所有文本
batch_texts = []
for i in range(start_idx, end_idx):
for block in self.pages_data[i]:
batch_texts.append(block[0]) # block[0] = 原文
# 翻译
if self.translation and self.use_mupdf:
translation_list = at.Online_translation(
original_language=original_language,
target_language=target_language,
translation_type=translation_type,
texts_to_process=batch_texts
).translation()
elif self.translation and not self.use_mupdf:
# 离线翻译
translation_list = at.Offline_translation(
original_language=original_language,
target_language=target_language,
texts_to_process=batch_texts
).translation()
else:
translation_list = batch_texts
# 回填译文
idx_t = 0
for i in range(start_idx, end_idx):
for block in self.pages_data[i]:
# 在第三个位置添加翻译文本
block[2] = translation_list[idx_t]
idx_t += 1
start_idx += batch_size
def apply_translations_to_pdf(self):
"""
统一对 PDF 做“打码/打白 + 插入译文”操作
"""
for page_index, blocks in enumerate(self.pages_data):
page = self.doc.load_page(page_index)
for block in blocks:
original_text = block[0]
coords = block[1] # (x0, y0, x1, y1)
# 如果第三个元素是译文,则用之,否则用原文
translated_text = block[2] if len(block) >= 3 else original_text
if self.line_model:
angle = block[3] if len(block) > 3 else 0
else:
angle = 0
rect = fitz.Rect(*coords)
# 先尝试使用 Redact 遮盖
try:
page.add_redact_annot(rect)
page.apply_redactions()
except Exception as e:
# 若 Redact 失败,改用白色方块覆盖
annots = list(page.annots() or [])
if annots:
page.delete_annot(annots[-1])
try:
page.draw_rect(rect, color=(1, 1, 1), fill=(1, 1, 1))
except Exception as e2:
print(f"创建白色画布时发生错误: {e2}")
print(f"应用重编辑时发生错误: {e}")
page.insert_htmlbox(
rect,
translated_text,
css="""
* {
font-family: "Microsoft YaHei";
/* 这行可把内容改成粗体, 可写 "bold" 或数字 (100–900) */
font-weight: 100;
/* 这里可以使用标准 CSS 颜色写法, 例如 #FF0000、rgb() 等 */
color: #333333;
}
""",
rotate=angle
)
if __name__ == '__main__':
main_function(original_language='auto', target_language='zh', pdf_path='g2.pdf',en=30,bn=0).main()
================================================
FILE: EbookTranslator/LICENSE
================================================
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
================================================
FILE: EbookTranslator/README.md
================================================
English | [简体中文](https://github.com/CBIhalsen/PolyglotPDF/blob/main//README_CN.md) | [繁體中文](https://github.com/CBIhalsen/PolyglotPDF/blob/main/README_TW.md) | [日本語](https://github.com/CBIhalsen/PolyglotPDF/blob/main/README_JA.md) | [한국어](https://github.com/CBIhalsen/PolyglotPDF/blob/main/README_KO.md)
# PolyglotPDF
[](https://www.python.org/)
[](https://example.com)
[](https://www.latex-project.org/)
[](https://example.com)
[](https://example.com)
[](https://pymupdf.readthedocs.io/)
## Demo
<img src="https://github.com/CBIhalsen/PolyglotPDF/blob/main/static/demo.gif?raw=true" width="80%" height="40%">
## Speed comparison
<img src="https://github.com/CBIhalsen/PolyglotPDF/blob/main/static/Figure_1.png?raw=true" width="80%" height="40%">
### [🎬 Watch Full Video](https://github.com/CBIhalsen/PolyglotPDF/blob/main/demo.mp4)
llms has been added as the translation api of choice, Doubao ,Qwen ,deepseek v3 , gpt4-o-mini are recommended. The color space error can be resolved by filling the white areas in PDF files. The old text to text translation api has been removed.
In addition, consider adding arxiv search function and rendering arxiv papers after latex translation.
### Pasges show
<div style="display: flex; margin-bottom: 20px;">
<img src="https://github.com/CBIhalsen/PolyglotPDF/blob/main/static/page1.png?raw=true" width="40%" height="20%" style="margin-right: 20px;">
<img src="https://github.com/CBIhalsen/PolyglotPDF/blob/main/static/page2.jpeg?raw=true" width="40%" height="20%">
</div>
<div style="display: flex;">
<img src="https://github.com/CBIhalsen/PolyglotPDF/blob/main/static/page3.png?raw=true" width="40%" height="20%" style="margin-right: 20px;">
<img src="https://github.com/CBIhalsen/PolyglotPDF/blob/main/static/page4.png?raw=true" width="40%" height="20%">
</div>
# Chinese LLM API Application
## Doubao & Deepseek
Apply through Volcengine platform:
- Application URL: [Volcengine-Doubao](https://www.volcengine.com/product/doubao/)
- Available Models: Doubao, Deepseek series models
## Tongyi Qwen
Apply through Alibaba Cloud platform:
- Application URL: [Alibaba Cloud-Tongyi Qwen](https://cn.aliyun.com/product/tongyi?from_alibabacloud=&utm_content=se_1019997984)
- Available Models: Qwen-Max, Qwen-Plus series models
## Overview
PolyglotPDF(EbookTranslation) is an advanced PDF processing tool that employs specialized techniques for ultra-fast text, table, and formula recognition in PDF documents, typically completing processing within 1 second. It features OCR capabilities and layout-preserving translation, with full document translations usually completed within 10 seconds (speed may vary depending on the translation API provider).
## Features
- **Ultra-Fast Recognition**: Processes text, tables, and formulas in PDFs within ~1 second
- **Layout-Preserving Translation**: Maintains original document formatting while translating content
- **OCR Support**: Handles scanned documents efficiently
- **Text-based PDF**:No GPU required
- **Quick Translation**: Complete PDF translation in approximately 10 seconds
- **Flexible API Integration**: Compatible with various translation service providers
- **Web-based Comparison Interface**: Side-by-side comparison of original and translated documents
- **Enhanced OCR Capabilities**: Improved accuracy in text recognition and processing
- **Support for offline translation**: Use smaller translation model
## Installation and Setup
### There are several ways to use it. One is to install the library,
```bash
pip install EbookTranslator
```
Basic usage:
```bash
EbookTranslator your_file.pdf
```
Usage with parameters:
```bash
EbookTranslator your_file.pdf -o en -t zh -b 1 -e 10 -c /path/to/config.json -d 300
```
#### Using in Python Code
```python
from EbookTranslator import main_function
translator = main_function(
pdf_path="your_file.pdf",
original_language="en",
target_language="zh",
bn=1,
en=10,
config_path="/path/to/config.json",
DPI=300
)
translator.main()
```
## Parameter Description
| Parameter | Command Line Option | Description | Default Value |
|-----------|---------------------|-------------|---------------|
| `pdf_path` | Positional argument | PDF file path | Required |
| `original_language` | `-o, --original` | Source language | `auto` |
| `target_language` | `-t, --target` | Target language | `zh` |
| `bn` | `-b, --begin` | Starting page number | `1` |
| `en` | `-e, --end` | Ending page number | Last page of the document |
| `config_path` | `-c, --config` | Configuration file path | `config.json` in the current working directory |
| `DPI` | `-d, --dpi` | DPI for OCR mode | `72` |
#### Configuration File
The configuration file is a JSON file, by default located at `config.json` in the current working directory. If it doesn't exist, the program will use built-in default settings.
#### Configuration File Example
```json
{
"count": 4,
"PPC": 20,
"translation_services": {
"Doubao": {
"auth_key": "",
"model_name": ""
},
"Qwen": {
"auth_key": "",
"model_name": "qwen-plus"
},
"deepl": {
"auth_key": ""
},
"deepseek": {
"auth_key": "",
"model_name": "ep-20250218224909-gps4n"
},
"openai": {
"auth_key": "",
"model_name": "gpt-4o-mini"
},
"youdao": {
"app_key": "",
"app_secret": ""
}
},
"ocr_services": {
"tesseract": {
"path": "C:\\Program Files\\Tesseract-OCR\\tesseract.exe"
}
},
"default_services": {
"ocr_model": false,
"line_model": false,
"Enable_translation": true,
"Translation_api": "openai"
}
}
```
#### Configuration Options
- `translation_service`: Translation service provider (e.g., "google", "deepl", "baidu")
- `api_key`: Translation API key (if required)
- `translation_mode`: Translation mode, "online" or "offline"
- `ocr_enabled`: Whether to enable OCR recognition
- `tesseract_path`: Path to Tesseract OCR engine (if not in system PATH)
- `output_dir`: Output directory
- `language_codes`: Language code mapping
- `font_mapping`: Fonts corresponding to different languages
#### Output
Translated PDF files will be saved in the directory specified by `output_dir` (default is the `target` folder in the current working directory).
## License
MIT
## Use method for friendly UI interface
1. Clone the repository:
```bash
git clone https://github.com/CBIhalsen/PolyglotPDF.git
cd polyglotpdf
```
2. Install required packages:
```bash
pip install -r requirements.txt
```
3. Configure your API key in config.json. The alicloud translation API is not recommended.
4. Run the application:
```bash
python app.py
```
5. Access the web interface:
Open your browser and navigate to `http://127.0.0.1:8000`
## Requirements
- Python 3.8+
- deepl==1.17.0
- Flask==2.0.1
- Flask-Cors==5.0.0
- langdetect==1.0.9
- Pillow==10.2.0
- PyMuPDF==1.24.0
- pytesseract==0.3.10
- requests==2.31.0
- tiktoken==0.6.0
- Werkzeug==2.0.1
## Acknowledgments
This project leverages PyMuPDF's capabilities for efficient PDF processing and layout preservation.
## Upcoming Improvements
- PDF chat functionality
- Academic PDF search integration
- Optimization for even faster processing speeds
### Known Issues
- **Issue Description**: Error during text re-editing: `code=4: only Gray, RGB, and CMYK colorspaces supported`
- **Symptom**: Unsupported color space encountered during text block editing
- **Current Workaround**: Skip text blocks with unsupported color spaces
- **Proposed Solution**: Switch to OCR mode for entire pages containing unsupported color spaces
- **Example**: [View PDF sample with unsupported color spaces](https://github.com/CBIhalsen/PolyglotPDF/blob/main/static/colorspace_issue_sample.pdf)
### Font Optimization
Current font configuration in the `start` function of `main.py`:
```python
# Current configuration
css=f"* {{font-family:{get_font_by_language(self.target_language)};font-size:auto;color: #111111 ;font-weight:normal;}}"
```
You can optimize font display through the following methods:
1. **Modify Default Font Configuration**
```python
# Custom font styles
css=f"""* {{
font-family: {get_font_by_language(self.target_language)};
font-size: auto;
color: #111111;
font-weight: normal;
letter-spacing: 0.5px; # Adjust letter spacing
line-height: 1.5; # Adjust line height
}}"""
```
2. **Embed Custom Fonts**
You can embed custom fonts by following these steps:
- Place font files (.ttf, .otf) in the project's `fonts` directory
- Use `@font-face` to declare custom fonts in CSS
```python
css=f"""
@font-face {{
font-family: 'CustomFont';
src: url('fonts/your-font.ttf') format('truetype');
}}
* {{
font-family: 'CustomFont', {get_font_by_language(self.target_language)};
font-size: auto;
font-weight: normal;
}}
"""
```
### Basic Principles
This project follows similar basic principles as Adobe Acrobat DC's PDF editing, using PyMuPDF for text block recognition and manipulation:
- **Core Process**:
```python
# Get text blocks from the page
blocks = page.get_text("dict")["blocks"]
# Process each text block
for block in blocks:
if block.get("type") == 0: # text block
bbox = block["bbox"] # get text block boundary
text = ""
font_info = None
# Collect text and font information
for line in block["lines"]:
for span in line["spans"]:
text += span["text"] + " "
```
This approach directly processes PDF text blocks, maintaining the original layout while achieving efficient text extraction and modification.
- **Technical Choices**:
- Utilizes PyMuPDF for PDF parsing and editing
- Focuses on text processing
- Avoids complex operations like AI formula recognition, table processing, or page restructuring
- **Why Avoid Complex Processing**:
- AI recognition of formulas, tables, and PDF restructuring faces severe performance bottlenecks
- Complex AI processing leads to high computational costs
- Significantly increased processing time (potentially tens of seconds or more)
- Difficult to deploy at scale with low costs in production environments
- Not suitable for online services requiring quick response times
- **Project Scope**:
- This project only serves to demonstrate the correct approach for layout-preserved PDF translation and AI-assisted PDF reading. Converting PDF files to markdown format for large language models to read, in my opinion, is not a wise approach.
- Aims for optimal performance-to-cost ratio
- **Performance**:
- PolyglotPDF API response time: ~1 second per page
- Low computational resource requirements, suitable for scale deployment
- High cost-effectiveness for commercial applications
- * Contact author:
QQ: 1421243966
email: 1421243966@qq.com
Related questions answered and discussed:
QQ group:
1031477425
================================================
FILE: EbookTranslator/requirements.txt
================================================
deepl==1.17.0
Flask
flask-cors
Pillow==10.2.0
PyMuPDF==1.24.0
pytesseract==0.3.10
requests==2.31.0
Werkzeug==2.0.1
aiohttp
================================================
FILE: EbookTranslator/setup.py
================================================
from setuptools import setup, find_packages
with open("README.md", "r", encoding="utf-8") as fh:
long_description = fh.read()
setup(
name="EbookTranslator",
version="0.3.3",
author="Chen",
author_email="1421243966@qq.com",
description="The world's highest performing e-book retention layout translation library",
long_description=long_description, # 添加这一行
long_description_content_type="text/markdown",
url="https://github.com/1421243966/EbookTranslator", # 更新为您的实际GitHub仓库
packages=find_packages(),
classifiers=[
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"Intended Audience :: Education",
"Intended Audience :: Science/Research",
"Topic :: Text Processing :: Linguistic",
"Topic :: Utilities",
],
python_requires=">=3.6",
install_requires=[
"pymupdf>=1.18.0",
"Pillow>=8.0.0",
"pytesseract>=0.3.0",
"deepl>=1.17.0",
"requests>=2.25.0",
"Werkzeug>=2.0.0",
"aiohttp>=3.7.4",
],
entry_points={
"console_scripts": [
"EbookTranslator=EbookTranslator.cli:main",
],
},
include_package_data=True,
keywords=["ebook", "translation", "pdf", "ocr", "nlp", "language"],
project_urls={
"Bug Reports": "https://github.com/1421243966/EbookTranslator/issues",
"Source": "https://github.com/1421243966/EbookTranslator",
"Documentation": "https://github.com/1421243966/EbookTranslator#readme",
},
)
================================================
FILE: LICENSE
================================================
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
================================================
FILE: LLMS_translation.py
================================================
import asyncio
import aiohttp
import re
import requests
import load_config
class AI302_translation:
def __init__(self):
config = load_config.load_config()
self.api_key = config['translation_services']['AI302']['auth_key']
self.url = "https://api.302.ai/v1/chat/completions"
self.headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
self.model = config['translation_services']['AI302']['model_name']
# 从配置中读取翻译提示词
self.prompt_template = config.get('translation_prompt', {}).get('system_prompt',
'You are a professional translator. Translate from {original_lang} to {target_lang}. Return only the translation without explanations or notes.')
async def translate_single(self, session, text, original_lang, target_lang):
"""单个文本的异步翻译"""
payload = {
"model": self.model,
"messages": [
{
"role": "system",
"content": self.prompt_template.format(original_lang=original_lang, target_lang=target_lang)
},
{
"role": "user",
"content": text
}
],
"response_format": {
"type": "text"
},
"temperature": 0.3,
"top_p": 0.9
}
try:
async with session.post(self.url, headers=self.headers, json=payload) as response:
if response.status == 200:
result = await response.json()
return result['choices'][0]['message']['content'].strip()
else:
print(f"Error: {response.status}")
return ""
except Exception as e:
print(f"Error in translation: {e}")
return ""
async def translate(self, texts, original_lang, target_lang):
"""异步批量翻译"""
print(f"Starting 302.ai translation of {len(texts)} texts")
async with aiohttp.ClientSession() as session:
tasks = [
self.translate_single(session, text, original_lang, target_lang)
for text in texts
]
results = await asyncio.gather(*tasks)
print(f"302.ai translation completed, {len(results)} texts translated")
return results
class Openai_translation:
def __init__(self):
config = load_config.load_config()
self.api_key = config['translation_services']['openai']['auth_key']
self.url = "https://api.openai.com/v1/chat/completions"
self.headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
self.model = config['translation_services']['openai']['model_name']
# 从配置中读取翻译提示词
self.prompt_template = config.get('translation_prompt', {}).get('system_prompt',
'You are a professional translator. Translate from {original_lang} to {target_lang}. Return only the translation without explanations or notes.')
async def translate_single(self, session, text, original_lang, target_lang):
"""单个文本的异步翻译"""
payload = {
"model": self.model,
"messages": [
{
"role": "system",
"content": self.prompt_template.format(original_lang=original_lang, target_lang=target_lang)
},
{
"role": "user",
"content": text
}
],
"response_format": {
"type": "text"
},
"temperature": 0.3,
"top_p": 0.9
}
try:
async with session.post(self.url, headers=self.headers, json=payload) as response:
if response.status == 200:
result = await response.json()
return result['choices'][0]['message']['content'].strip()
else:
print(f"Error: {response.status}")
return ""
except Exception as e:
print(f"Error in translation: {e}")
return ""
async def translate(self, texts, original_lang, target_lang):
"""异步批量翻译"""
async with aiohttp.ClientSession() as session:
tasks = [
self.translate_single(session, text, original_lang, target_lang)
for text in texts
]
return await asyncio.gather(*tasks)
# 添加Bing翻译类
class Bing_translation:
def __init__(self):
self.session = requests.Session()
self.endpoint = "https://www.bing.com/translator"
self.headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0",
}
self.lang_map = {"zh": "zh-Hans"}
async def find_sid(self):
"""获取必要的会话参数"""
loop = asyncio.get_event_loop()
response = await loop.run_in_executor(None, lambda: self.session.get(self.endpoint, headers=self.headers))
response.raise_for_status()
url = response.url[:-10]
ig = re.findall(r"\"ig\":\"(.*?)\"", response.text)[0]
iid = re.findall(r"data-iid=\"(.*?)\"", response.text)[-1]
key, token = re.findall(
r"params_AbusePreventionHelper\s=\s\[(.*?),\"(.*?)\",", response.text
)[0]
return url, ig, iid, key, token
async def translate_single(self, session, text, original_lang, target_lang):
"""单个文本的异步翻译"""
if not text or not text.strip():
return ""
# 处理语言代码映射
lang_in = self.lang_map.get(original_lang, original_lang)
lang_out = self.lang_map.get(target_lang, target_lang)
# 自动语言检测处理
if lang_in == "auto":
lang_in = "auto-detect"
# Bing翻译最大长度限制
text = text[:1000]
try:
url, ig, iid, key, token = await self.find_sid()
# 通过异步HTTP请求执行翻译
async with session.post(
f"{url}ttranslatev3?IG={ig}&IID={iid}",
data={
"fromLang": lang_in,
"to": lang_out,
"text": text,
"token": token,
"key": key,
},
headers=self.headers,
) as response:
if response.status == 200:
result = await response.json()
return result[0]["translations"][0]["text"]
else:
error_text = await response.text()
print(f"Bing翻译错误: {response.status}, 详情: {error_text}")
return ""
except Exception as e:
print(f"Bing翻译过程中发生错误: {e}")
return ""
async def translate(self, texts, original_lang, target_lang):
"""异步批量翻译"""
print(f"开始Bing翻译,共 {len(texts)} 个文本")
async with aiohttp.ClientSession() as session:
tasks = []
for i, text in enumerate(texts):
# 添加延迟以避免请求过快被屏蔽
await asyncio.sleep(0.5 * (i % 3)) # 每3个请求一组,每组之间间隔0.5秒
tasks.append(self.translate_single(session, text, original_lang, target_lang))
results = await asyncio.gather(*tasks)
print(f"Bing翻译完成,共翻译 {len(results)} 个文本")
return results
class Deepseek_translation:
def __init__(self):
config = load_config.load_config()
self.api_key = config['translation_services']['deepseek']['auth_key']
self.url = "https://ark.cn-beijing.volces.com/api/v3/chat/completions"
self.headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
self.model = config['translation_services']['deepseek']['model_name']
# 从配置中读取翻译提示词
self.prompt_template = config.get('translation_prompt', {}).get('system_prompt',
'You are a professional translator. Translate from {original_lang} to {target_lang}. Return only the translation without explanations or notes.')
async def translate_single(self, session, text, original_lang, target_lang):
"""单个文本的异步翻译"""
payload = {
"model": self.model,
"messages": [
{
"role": "system",
"content": self.prompt_template.format(original_lang=original_lang, target_lang=target_lang)
},
{
"role": "user",
"content": text
}
],
"temperature": 0.3,
"top_p": 0.9
}
try:
async with session.post(self.url, headers=self.headers, json=payload) as response:
if response.status == 200:
result = await response.json()
return result['choices'][0]['message']['content'].strip()
else:
print(f"Error: {response.status}")
return ""
except Exception as e:
print(f"Error in translation: {e}")
return ""
async def translate(self, texts, original_lang, target_lang):
"""异步批量翻译"""
async with aiohttp.ClientSession() as session:
tasks = [
self.translate_single(session, text, original_lang, target_lang)
for text in texts
]
return await asyncio.gather(*tasks)
class Doubao_translation:
def __init__(self):
config = load_config.load_config()
self.api_key = config['translation_services']['Doubao']['auth_key']
self.url = "https://ark.cn-beijing.volces.com/api/v3/chat/completions"
self.headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
self.model = config['translation_services']['Doubao']['model_name']
# 从配置中读取翻译提示词
self.prompt_template = config.get('translation_prompt', {}).get('system_prompt',
'You are a professional translator. Translate from {original_lang} to {target_lang}. Return only the translation without explanations or notes.')
async def translate_single(self, session, text, original_lang, target_lang):
"""单个文本的异步翻译"""
payload = {
"model": self.model,
"messages": [
{
"role": "system",
"content": self.prompt_template.format(original_lang=original_lang, target_lang=target_lang)
},
{
"role": "user",
"content": text
}
],
"temperature": 0.3,
"top_p": 0.9
}
try:
async with session.post(self.url, headers=self.headers, json=payload) as response:
if response.status == 200:
result = await response.json()
return result['choices'][0]['message']['content'].strip()
else:
print(f"Error: {response.status}")
return ""
except Exception as e:
print(f"Error in translation: {e}")
return ""
async def translate(self, texts, original_lang, target_lang):
"""异步批量翻译"""
async with aiohttp.ClientSession() as session:
tasks = [
self.translate_single(session, text, original_lang, target_lang)
for text in texts
]
return await asyncio.gather(*tasks)
class Qwen_translation:
def __init__(self):
config = load_config.load_config()
self.api_key = config['translation_services']['Qwen']['auth_key']
self.url = "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions"
self.headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
self.model = config['translation_services']['Qwen']['model_name']
# 从配置中读取翻译提示词
self.prompt_template = config.get('translation_prompt', {}).get('system_prompt',
'You are a professional translator. Translate from {original_lang} to {target_lang}. Return only the translation without explanations or notes.')
async def translate_single(self, session, text, original_lang, target_lang):
"""单个文本的异步翻译"""
payload = {
"model": self.model,
"messages": [
{
"role": "system",
"content": self.prompt_template.format(original_lang=original_lang, target_lang=target_lang)
},
{
"role": "user",
"content": text
}
],
"temperature": 0.3,
"top_p": 0.9
}
try:
async with session.post(self.url, headers=self.headers, json=payload) as response:
if response.status == 200:
result = await response.json()
return result['choices'][0]['message']['content'].strip()
else:
print(f"Error: {response.status}")
return ""
except Exception as e:
print(f"Error in translation: {e}")
return ""
async def translate(self, texts, original_lang, target_lang):
"""异步批量翻译"""
async with aiohttp.ClientSession() as session:
tasks = [
self.translate_single(session, text, original_lang, target_lang)
for text in texts
]
return await asyncio.gather(*tasks)
class Grok_translation:
def __init__(self):
config = load_config.load_config()
# 修改键名为大写的'Grok'以匹配其他API命名风格
self.api_key = config['translation_services']['Grok']['auth_key']
self.url = "https://api-proxy.me/xai/v1/chat/completions"
self.headers = {
"Content-Type": "application/json",
"X-Api-Key": self.api_key,
"Authorization": f"Bearer {self.api_key}"
}
self.model = config['translation_services']['Grok']['model_name']
# 从配置中读取翻译提示词
self.prompt_template = config.get('translation_prompt', {}).get('system_prompt',
'You are a professional translator. Translate from {original_lang} to {target_lang}. Return only the translation without explanations or notes.')
async def translate_single(self, session, text, original_lang, target_lang):
"""单个文本的异步翻译"""
payload = {
"model": self.model,
"messages": [
{
"role": "system",
"content": self.prompt_template.format(original_lang=original_lang, target_lang=target_lang)
},
{
"role": "user",
"content": text
}
],
"temperature": 0.3,
"stream": False
}
try:
async with session.post(self.url, headers=self.headers, json=payload) as response:
if response.status == 200:
result = await response.json()
translated_text = result['choices'][0]['message']['content'].strip()
# 添加调试日志
#print(f"Grok translated: '{text[:30]}...' -> '{translated_text[:30]}...'")
return translated_text
else:
error_text = await response.text()
print(f"Error: {response.status}, Details: {error_text}")
return ""
except Exception as e:
print(f"Error in translation: {e}")
return ""
async def translate(self, texts, original_lang, target_lang):
"""异步批量翻译"""
print(f"Starting Grok translation of {len(texts)} texts")
async with aiohttp.ClientSession() as session:
tasks = [
self.translate_single(session, text, original_lang, target_lang)
for text in texts
]
results = await asyncio.gather(*tasks)
print(f"Grok translation completed, {len(results)} texts translated")
return results
class ThirdParty_translation:
def __init__(self):
config = load_config.load_config()
self.api_key = config['translation_services']['ThirdParty']['auth_key']
self.url = config['translation_services']['ThirdParty']['api_url']
self.headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
self.model = config['translation_services']['ThirdParty']['model_name']
# 从配置中读取翻译提示词
self.prompt_template = config.get('translation_prompt', {}).get('system_prompt',
'You are a professional translator. Translate from {original_lang} to {target_lang}. Return only the translation without explanations or notes.')
async def translate_single(self, session, text, original_lang, target_lang):
"""单个文本的异步翻译"""
payload = {
"model": self.model,
"messages": [
{
"role": "system",
"content": self.prompt_template.format(original_lang=original_lang, target_lang=target_lang)
},
{
"role": "user",
"content": text
}
],
"temperature": 0.3,
"stream": False
}
try:
async with session.post(self.url, headers=self.headers, json=payload) as response:
if response.status == 200:
result = await response.json()
translated_text = result['choices'][0]['message']['content'].strip()
# 添加调试日志
#print(f"ThirdParty translated: '{text[:30]}...' -> '{translated_text[:30]}...'")
return translated_text
else:
error_text = await response.text()
print(f"Error: {response.status}, Details: {error_text}")
return ""
except Exception as e:
print(f"Error in translation: {e}")
return ""
async def translate(self, texts, original_lang, target_lang):
"""异步批量翻译"""
print(f"Starting ThirdParty translation of {len(texts)} texts")
async with aiohttp.ClientSession() as session:
tasks = [
self.translate_single(session, text, original_lang, target_lang)
for text in texts
]
results = await asyncio.gather(*tasks)
print(f"ThirdParty translation completed, {len(results)} texts translated")
return results
class GLM_translation:
def __init__(self):
config = load_config.load_config()
self.api_key = config['translation_services']['GLM']['auth_key']
self.url = "https://open.bigmodel.cn/api/paas/v4/chat/completions"
self.headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
self.model = config['translation_services']['GLM']['model_name']
# 从配置中读取翻译提示词
self.prompt_template = config.get('translation_prompt', {}).get('system_prompt',
'You are a professional translator. Translate from {original_lang} to {target_lang}. Return only the translation without explanations or notes.')
async def translate_single(self, session, text, original_lang, target_lang):
"""单个文本的异步翻译"""
# 过滤控制字符,解决GLM 1213错误
cleaned_text = re.sub(r'[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F]', '', text)
if not cleaned_text.strip():
return ""
payload = {
"model": self.model,
"messages": [
{
"role": "system",
"content": self.prompt_template.format(original_lang=original_lang, target_lang=target_lang)
},
{
"role": "user",
"content": cleaned_text
}
],
"temperature": 0.3,
"top_p": 0.7,
"do_sample": False
}
try:
async with session.post(self.url, headers=self.headers, json=payload) as response:
if response.status == 200:
result = await response.json()
return result['choices'][0]['message']['content'].strip()
else:
error_text = await response.text()
print(f"Error: {response.status}, Details: {error_text}")
return ""
except Exception as e:
print(f"Error in GLM translation: {e}")
return ""
async def translate(self, texts, original_lang, target_lang):
"""异步批量翻译"""
print(f"Starting GLM translation of {len(texts)} texts")
async with aiohttp.ClientSession() as session:
tasks = [
self.translate_single(session, text, original_lang, target_lang)
for text in texts
]
results = await asyncio.gather(*tasks)
print(f"GLM translation completed, {len(results)} texts translated")
return results
# 测试代码
async def main():
texts = [
"Hello, how are you?",
"What's the weather like
gitextract_n0dc3ude/ ├── .dockerignore ├── .gitignore ├── All_Translation.py ├── Bing_translation.py ├── Deepl_Translation.py ├── Dockerfile ├── EbookTranslator/ │ ├── EbookTranslator/ │ │ ├── All_Translation.py │ │ ├── Deepl_Translation.py │ │ ├── LLMS_translation.py │ │ ├── YouDao_translation.py │ │ ├── __init__.py │ │ ├── cli.py │ │ ├── convert2pdf.py │ │ ├── load_config.py │ │ └── main_function.py │ ├── LICENSE │ ├── README.md │ ├── requirements.txt │ └── setup.py ├── LICENSE ├── LLMS_translation.py ├── OldMain.py ├── README.md ├── README_CN.md ├── README_JA.md ├── README_KO.md ├── README_TW.md ├── Subset_Font.py ├── YouDao_translation.py ├── app.py ├── build.py ├── config.json ├── convert2pdf.py ├── docker-compose.yml ├── download_model.py ├── get_new_blocks.py ├── index.html ├── languagedetect.py ├── load_config.py ├── main.py ├── merge_pdf.py ├── pdf_thumbnail.py ├── pdfviewer.html ├── pdfviewer2.html ├── recent.json ├── requirements.txt ├── static/ │ ├── 1.js │ ├── 2.js │ ├── 3.js │ ├── 4.js │ ├── i18n.js │ ├── main.css │ ├── setup.css │ ├── setup.js │ └── thumbnail/ │ └── ...txt └── update_recent.py
SYMBOL INDEX (272 symbols across 30 files)
FILE: All_Translation.py
function retry_on_error (line 21) | def retry_on_error(max_retries=2, delay=1):
function process_translation_queue (line 60) | def process_translation_queue():
function ensure_queue_processor (line 89) | def ensure_queue_processor():
class Online_translation (line 96) | class Online_translation:
method __init__ (line 97) | def __init__(self, original_language, target_language, translation_typ...
method run_async (line 106) | def run_async(self, coro):
method _run_coro_with_semaphore (line 119) | async def _run_coro_with_semaphore(self, coro):
method translation (line 124) | def translation(self):
method deepl_translation (line 154) | def deepl_translation(self):
method youdao_translation (line 163) | def youdao_translation(self):
method bing_translation (line 172) | def bing_translation(self):
method AI302_translation (line 186) | async def AI302_translation(self):
method openai_translation (line 196) | async def openai_translation(self):
method deepseek_translation (line 206) | async def deepseek_translation(self):
method Doubao_translation (line 216) | async def Doubao_translation(self):
method Qwen_translation (line 226) | async def Qwen_translation(self):
method Grok_translation (line 236) | async def Grok_translation(self):
method ThirdParty_translation (line 251) | async def ThirdParty_translation(self):
method GLM_translation (line 266) | async def GLM_translation(self):
function cleanup (line 284) | def cleanup():
function split_text_to_fit_token_limit (line 293) | def split_text_to_fit_token_limit(text, encoder, index_text, max_length=...
function process_texts (line 312) | def process_texts(texts, encoder):
function calculate_split_points (line 319) | def calculate_split_points(processed_texts, max_tokens=425):
FILE: Bing_translation.py
function translate (line 9) | def translate(texts, original_lang, target_lang):
function translate_with_threadpool (line 33) | def translate_with_threadpool(texts, original_lang, target_lang, max_wor...
function translate_with_asyncio (line 57) | def translate_with_asyncio(texts, original_lang, target_lang):
function split_text_intelligently (line 75) | def split_text_intelligently(text, max_length=1000):
class BingTranslator (line 122) | class BingTranslator:
method __init__ (line 132) | def __init__(self, lang_in, lang_out, model=None, ignore_cache=False):
method find_sid (line 149) | def find_sid(self):
method do_translate (line 178) | def do_translate(self, text):
class AsyncBingTranslator (line 222) | class AsyncBingTranslator:
method __init__ (line 231) | def __init__(self, lang_in, lang_out):
method find_sid (line 243) | async def find_sid(self, session):
method translate_text (line 272) | async def translate_text(self, session, text):
method translate_batch (line 330) | async def translate_batch(self, texts, batch_size=10, max_concurrent=5):
FILE: Deepl_Translation.py
function translate (line 3) | def translate(texts,original_lang,target_lang):
FILE: EbookTranslator/EbookTranslator/All_Translation.py
class Online_translation (line 15) | class Online_translation:
method __init__ (line 16) | def __init__(self, original_language, target_language, translation_typ...
method run_async (line 23) | def run_async(self, coro):
method translation (line 27) | def translation(self):
method deepl_translation (line 62) | def deepl_translation(self):
method youdao_translation (line 69) | def youdao_translation(self):
method openai_translation (line 77) | async def openai_translation(self):
method deepseek_translation (line 86) | async def deepseek_translation(self):
method Doubao_translation (line 94) | async def Doubao_translation(self):
method Qwen_translation (line 102) | async def Qwen_translation(self):
method Grok_translation (line 110) | async def Grok_translation(self):
method ThirdParty_translation (line 124) | async def ThirdParty_translation(self):
method GLM_translation (line 138) | async def GLM_translation(self):
method bing_translation (line 152) | async def bing_translation(self):
function split_text_to_fit_token_limit (line 168) | def split_text_to_fit_token_limit(text, encoder, index_text, max_length=...
function process_texts (line 188) | def process_texts(texts, encoder):
function calculate_split_points (line 197) | def calculate_split_points(processed_texts, max_tokens=425):
function translate (line 216) | def translate(texts,original_language,target_language):
function batch_translate (line 236) | def batch_translate(processed_texts, split_points,original_language,targ...
FILE: EbookTranslator/EbookTranslator/Deepl_Translation.py
function translate (line 3) | def translate(texts,original_lang,target_lang):
FILE: EbookTranslator/EbookTranslator/LLMS_translation.py
class Openai_translation (line 8) | class Openai_translation:
method __init__ (line 9) | def __init__(self):
method translate_single (line 19) | async def translate_single(self, session, text, original_lang, target_...
method translate (line 52) | async def translate(self, texts, original_lang, target_lang):
class Bing_translation (line 62) | class Bing_translation:
method __init__ (line 63) | def __init__(self):
method find_sid (line 71) | async def find_sid(self):
method translate_single (line 84) | async def translate_single(self, session, text, original_lang, target_...
method translate (line 126) | async def translate(self, texts, original_lang, target_lang):
class Deepseek_translation (line 140) | class Deepseek_translation:
method __init__ (line 141) | def __init__(self):
method translate_single (line 151) | async def translate_single(self, session, text, original_lang, target_...
method translate (line 181) | async def translate(self, texts, original_lang, target_lang):
class Doubao_translation (line 189) | class Doubao_translation:
method __init__ (line 190) | def __init__(self):
method translate_single (line 200) | async def translate_single(self, session, text, original_lang, target_...
method translate (line 230) | async def translate(self, texts, original_lang, target_lang):
class Qwen_translation (line 238) | class Qwen_translation:
method __init__ (line 239) | def __init__(self):
method translate_single (line 249) | async def translate_single(self, session, text, original_lang, target_...
method translate (line 279) | async def translate(self, texts, original_lang, target_lang):
class Grok_translation (line 288) | class Grok_translation:
method __init__ (line 289) | def __init__(self):
method translate_single (line 301) | async def translate_single(self, session, text, original_lang, target_...
method translate (line 331) | async def translate(self, texts, original_lang, target_lang):
class ThirdParty_translation (line 340) | class ThirdParty_translation:
method __init__ (line 341) | def __init__(self):
method translate_single (line 351) | async def translate_single(self, session, text, original_lang, target_...
method translate (line 385) | async def translate(self, texts, original_lang, target_lang):
class GLM_translation (line 397) | class GLM_translation:
method __init__ (line 398) | def __init__(self):
method translate_single (line 408) | async def translate_single(self, session, text, original_lang, target_...
method translate (line 440) | async def translate(self, texts, original_lang, target_lang):
function main (line 453) | async def main():
FILE: EbookTranslator/EbookTranslator/YouDao_translation.py
function translate (line 8) | def translate(texts,original_lang, target_lang):
FILE: EbookTranslator/EbookTranslator/cli.py
function main (line 13) | def main():
FILE: EbookTranslator/EbookTranslator/convert2pdf.py
function convert_to_pdf (line 5) | def convert_to_pdf(input_file, output_file=None):
FILE: EbookTranslator/EbookTranslator/load_config.py
function get_working_dir (line 8) | def get_working_dir() -> Path:
function resolve_path (line 22) | def resolve_path(path: str) -> Path:
function load_config (line 40) | def load_config(config_path: Optional[str] = None) -> Optional[Dict]:
function get_file_path (line 88) | def get_file_path(filename: str) -> Path:
FILE: EbookTranslator/EbookTranslator/main_function.py
function get_font_by_language (line 21) | def get_font_by_language(target_language):
function is_math (line 32) | def is_math(text,pag_num,font_info):
function line_non_text (line 143) | def line_non_text(text):
function is_non_text (line 164) | def is_non_text(text):
class main_function (line 185) | class main_function:
method __init__ (line 186) | def __init__(self, pdf_path,
method resolve_path (line 224) | def resolve_path(pdf_path: str) -> str:
method get_config (line 246) | def get_config(self,config_path=None):
method main (line 252) | def main(self):
method start (line 349) | def start(self, image, pag_num):
method batch_translate_pages_data (line 526) | def batch_translate_pages_data(self, original_language, target_language,
method apply_translations_to_pdf (line 577) | def apply_translations_to_pdf(self):
FILE: LLMS_translation.py
class AI302_translation (line 8) | class AI302_translation:
method __init__ (line 9) | def __init__(self):
method translate_single (line 22) | async def translate_single(self, session, text, original_lang, target_...
method translate (line 55) | async def translate(self, texts, original_lang, target_lang):
class Openai_translation (line 67) | class Openai_translation:
method __init__ (line 68) | def __init__(self):
method translate_single (line 81) | async def translate_single(self, session, text, original_lang, target_...
method translate (line 114) | async def translate(self, texts, original_lang, target_lang):
class Bing_translation (line 124) | class Bing_translation:
method __init__ (line 125) | def __init__(self):
method find_sid (line 133) | async def find_sid(self):
method translate_single (line 146) | async def translate_single(self, session, text, original_lang, target_...
method translate (line 188) | async def translate(self, texts, original_lang, target_lang):
class Deepseek_translation (line 202) | class Deepseek_translation:
method __init__ (line 203) | def __init__(self):
method translate_single (line 216) | async def translate_single(self, session, text, original_lang, target_...
method translate (line 246) | async def translate(self, texts, original_lang, target_lang):
class Doubao_translation (line 254) | class Doubao_translation:
method __init__ (line 255) | def __init__(self):
method translate_single (line 268) | async def translate_single(self, session, text, original_lang, target_...
method translate (line 298) | async def translate(self, texts, original_lang, target_lang):
class Qwen_translation (line 306) | class Qwen_translation:
method __init__ (line 307) | def __init__(self):
method translate_single (line 320) | async def translate_single(self, session, text, original_lang, target_...
method translate (line 350) | async def translate(self, texts, original_lang, target_lang):
class Grok_translation (line 359) | class Grok_translation:
method __init__ (line 360) | def __init__(self):
method translate_single (line 375) | async def translate_single(self, session, text, original_lang, target_...
method translate (line 409) | async def translate(self, texts, original_lang, target_lang):
class ThirdParty_translation (line 421) | class ThirdParty_translation:
method __init__ (line 422) | def __init__(self):
method translate_single (line 435) | async def translate_single(self, session, text, original_lang, target_...
method translate (line 469) | async def translate(self, texts, original_lang, target_lang):
class GLM_translation (line 481) | class GLM_translation:
method __init__ (line 482) | def __init__(self):
method translate_single (line 495) | async def translate_single(self, session, text, original_lang, target_...
method translate (line 532) | async def translate(self, texts, original_lang, target_lang):
function main (line 545) | async def main():
FILE: OldMain.py
function get_font_by_language (line 29) | def get_font_by_language(target_language):
function is_math (line 40) | def is_math(text, page_num,font_info):
function line_non_text (line 151) | def line_non_text(text):
function is_non_text (line 172) | def is_non_text(text):
class main_function (line 193) | class main_function:
method __init__ (line 194) | def __init__(self, pdf_path,
method main (line 221) | def main(self):
method start (line 308) | def start(self, image, pag_num):
method batch_translate_pages_data (line 485) | def batch_translate_pages_data(self, original_language, target_language,
method apply_translations_to_pdf (line 537) | def apply_translations_to_pdf(self):
FILE: Subset_Font.py
function download_font_from_github (line 8) | def download_font_from_github(language, font_filename, target_path):
function check_glyph_coverage (line 60) | def check_glyph_coverage(font, text):
function subset_font (line 75) | def subset_font(in_font_path, out_font_path, text, language):
FILE: YouDao_translation.py
function translate (line 8) | def translate(texts,original_lang, target_lang):
FILE: app.py
class ThreadedWSGIServer (line 22) | class ThreadedWSGIServer(ThreadingMixIn, BaseWSGIServer):
function get_app_data_dir (line 31) | def get_app_data_dir():
function serve_static (line 82) | def serve_static(filename):
function read_index (line 101) | def read_index():
function read_pdfviewer (line 105) | def read_pdfviewer():
function read_pdfviewer2 (line 115) | def read_pdfviewer2():
function upload_file (line 126) | def upload_file():
function translate_files (line 194) | def translate_files():
function delete_article (line 261) | def delete_article():
function delete_batch (line 292) | def delete_batch():
function save_settings (line 337) | def save_settings():
function get_default_services_route (line 360) | def get_default_services_route():
function get_recent (line 390) | def get_recent():
function get_config_json (line 401) | def get_config_json():
function update_config_json (line 414) | def update_config_json():
function save_all (line 447) | def save_all():
function get_config_api (line 465) | def get_config_api():
function update_config_api (line 473) | def update_config_api():
function translate_file (line 507) | def translate_file():
function update_translation_status (line 527) | def update_translation_status(filename, status):
function reload_config (line 547) | def reload_config():
class ServerThread (line 576) | class ServerThread(Thread):
method __init__ (line 578) | def __init__(self, flask_app, host="127.0.0.1", port=12226):
method run (line 585) | def run(self):
method shutdown (line 590) | def shutdown(self):
function open_browser (line 595) | def open_browser():
function on_exit (line 601) | def on_exit():
FILE: build.py
function main (line 129) | def main():
FILE: convert2pdf.py
function convert_to_pdf (line 5) | def convert_to_pdf(input_file, output_file=None):
FILE: download_model.py
function download_file (line 30) | def download_file(url, dest_folder, file_name):
function download_model_files (line 41) | def download_model_files(model_name):
FILE: get_new_blocks.py
function snap_angle_func (line 21) | def snap_angle_func(raw_angle):
function horizontal_merge (line 38) | def horizontal_merge(
function merge_lines (line 157) | def merge_lines(lines_data, check_font_size=False, check_font_name=True,...
function is_math (line 535) | def is_math(font_info_list, text_len, text, font_size):
function merge_adjacent_math_lines (line 586) | def merge_adjacent_math_lines(lines):
function get_new_blocks (line 720) | def get_new_blocks(page, pdf_path=None, page_num=None):
FILE: load_config.py
class ConfigError (line 10) | class ConfigError(Exception):
function get_app_data_dir (line 16) | def get_app_data_dir():
function get_file_path (line 43) | def get_file_path(filename: str) -> Path:
function read_json_file (line 62) | def read_json_file(filename: str) -> Any:
function write_json_file (line 83) | def write_json_file(filename: str, data: Any) -> None:
function load_config (line 107) | def load_config(force_reload=False) -> Optional[Dict]:
function load_recent (line 155) | def load_recent() -> Optional[List]:
function add_new_entry (line 182) | def add_new_entry(new_entry: Dict) -> bool:
function update_count (line 205) | def update_count() -> bool:
function update_file_status (line 226) | def update_file_status(index: int, read: Optional[bool] = None, statue: ...
function delete_entry (line 252) | def delete_entry(index: int) -> bool:
function decrease_count (line 316) | def decrease_count() -> bool:
function update_default_services (line 336) | def update_default_services(translation: Optional[bool] = None,
function get_default_services (line 370) | def get_default_services() -> Optional[Dict]:
function save_config (line 400) | def save_config(config):
FILE: main.py
function get_current_config (line 18) | def get_current_config():
function decimal_to_hex_color (line 22) | def decimal_to_hex_color(decimal_color):
function is_math (line 35) | def is_math(text, page_num,font_info):
function line_non_text (line 38) | def line_non_text(text):
function is_non_text (line 42) | def is_non_text(text):
class main_function (line 45) | class main_function:
method __init__ (line 46) | def __init__(self, pdf_path,
method main (line 81) | def main(self):
method start (line 219) | def start(self, image, pag_num):
method batch_translate_pages_data (line 354) | def batch_translate_pages_data(self, original_language, target_language,
method apply_translations_to_pdf (line 405) | def apply_translations_to_pdf(self):
method subset_font (line 609) | def subset_font(self, in_font_path, out_font_path,text):
FILE: merge_pdf.py
function merge_pdfs_horizontally (line 4) | def merge_pdfs_horizontally(pdf1_path, pdf2_path, output_path, spacing=0):
FILE: pdf_thumbnail.py
function create_pdf_thumbnail (line 5) | def create_pdf_thumbnail(pdf_path, width=400):
FILE: static/1.js
function showHome (line 6) | function showHome() {
function showAllRecent (line 17) | function showAllRecent() {
function showSetup (line 29) | function showSetup() {
function showUpload (line 48) | function showUpload() {
function showSettings (line 59) | function showSettings() {
function loadArticles (line 64) | async function loadArticles(isLimited,first_reload) {
function showToast (line 197) | function showToast(message) {
function showMenu (line 210) | function showMenu(event, article) {
function open_bilingual (line 241) | function open_bilingual(articleId,article_name,article_tl,article_ol) {
function showToast (line 248) | function showToast(message) {
function closeUploadModal (line 265) | function closeUploadModal() {
FILE: static/2.js
function closeSettings (line 16) | function closeSettings() {
function getValue (line 22) | function getValue() {
function getValue2 (line 27) | function getValue2() {
function getecount (line 34) | function getecount() {
FILE: static/3.js
function triggerFileInput (line 5) | function triggerFileInput() {
function handleFiles (line 37) | async function handleFiles(input) {
function createFileItemUI (line 108) | function createFileItemUI(fileId, file) {
function removeFile (line 136) | function removeFile(fileId) {
function showError (line 192) | function showError(message) {
function createFileItemUI (line 198) | function createFileItemUI(fileId, file) {
function uploadFile (line 217) | async function uploadFile(fileId) {
function formatFileSize (line 250) | function formatFileSize(bytes) {
function showError (line 258) | function showError(message) {
function handleNextStep (line 262) | async function handleNextStep() {
function updatecount (line 349) | function updatecount() {
function deleteArticle (line 357) | function deleteArticle(articleId) {
FILE: static/4.js
function showBatchModal (line 6) | function showBatchModal() {
function closeBatchModal (line 12) | function closeBatchModal() {
function loadBatchData (line 19) | async function loadBatchData() {
function toggleSelectAll (line 84) | function toggleSelectAll() {
function handleBatchDelete (line 108) | async function handleBatchDelete() {
function handleMindMap (line 145) | function handleMindMap() {
function handleSummary (line 157) | function handleSummary() {
FILE: static/setup.js
function initializeUI (line 10) | function initializeUI(data) {
function createServiceSection (line 119) | function createServiceSection(serviceName, config) {
function saveall (line 179) | async function saveall() {
function collectConfig (line 234) | function collectConfig() {
function loadTranslationServices (line 305) | function loadTranslationServices(config) {
FILE: update_recent.py
function parse_merged_filename (line 10) | def parse_merged_filename(filename: str) -> Dict[str, str]:
function get_file_info (line 34) | def get_file_info(file_path: str) -> Dict[str, Any]:
function update_config_count (line 55) | def update_config_count(count: int) -> bool:
function validate_json_file (line 88) | def validate_json_file(file_path: str) -> bool:
function update_recent_json (line 108) | def update_recent_json():
Condensed preview — 56 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (598K chars).
[
{
"path": ".dockerignore",
"chars": 389,
"preview": "# 忽略 Git 相关文件\n.git\n.gitignore\n\n# 忽略 Python 缓存和编译文件\n__pycache__/\n**/__pycache__/\n*.pyc\n*.pyo\n*.pyd\n\n# 忽略虚拟环境相关文件夹\n.Python"
},
{
"path": ".gitignore",
"chars": 25,
"preview": "__pycache__\nvenv\n*.local\n"
},
{
"path": "All_Translation.py",
"chars": 13380,
"preview": "import time\r\nimport os\r\nimport Deepl_Translation as dt\r\nimport YouDao_translation as yt\r\nimport Bing_translation as bt\r\n"
},
{
"path": "Bing_translation.py",
"chars": 13712,
"preview": "import re\r\nimport requests\r\nimport time\r\nimport threading\r\nimport asyncio\r\nimport aiohttp\r\nfrom concurrent.futures impor"
},
{
"path": "Deepl_Translation.py",
"chars": 769,
"preview": "import deepl\nimport load_config\ndef translate(texts,original_lang,target_lang):\n\n# 你的 DeepL 授权密钥\n\n\n # 获取指定服务的认证信息\n\n\n "
},
{
"path": "Dockerfile",
"chars": 531,
"preview": "\n# 1. 使用官方 Python 3.9 的精简版镜像作为基础\nFROM python:3.9-slim\n\n# 2. 如果你需要一些系统库支持,可在此处安装\n# 比如安装 gcc、libssl-dev 等 (仅举例)\n# RUN a"
},
{
"path": "EbookTranslator/EbookTranslator/All_Translation.py",
"chars": 10200,
"preview": "import time\r\nimport os\r\nfrom .import Deepl_Translation as dt\r\nfrom .import YouDao_translation as yt\r\nfrom .import LLMS_t"
},
{
"path": "EbookTranslator/EbookTranslator/Deepl_Translation.py",
"chars": 775,
"preview": "import deepl\nfrom .import load_config\ndef translate(texts,original_lang,target_lang):\n\n# 你的 DeepL 授权密钥\n\n\n # 获取指定服务的认证"
},
{
"path": "EbookTranslator/EbookTranslator/LLMS_translation.py",
"chars": 20276,
"preview": "import asyncio\r\nimport aiohttp\r\nimport re\r\nimport requests\r\n\r\nfrom . import load_config\r\n\r\nclass Openai_translation:\r\n "
},
{
"path": "EbookTranslator/EbookTranslator/YouDao_translation.py",
"chars": 2745,
"preview": "import uuid\nimport requests\nimport hashlib\nimport time\nimport json\n\n\ndef translate(texts,original_lang, target_lang):\n "
},
{
"path": "EbookTranslator/EbookTranslator/__init__.py",
"chars": 226,
"preview": "\"\"\"\nEbookTranslator - 世界上性能最高的电子书保留布局翻译库\n The world's highest performing e-book retention layout translation library\n\"\"\""
},
{
"path": "EbookTranslator/EbookTranslator/cli.py",
"chars": 1497,
"preview": "#!/usr/bin/env python3\n\"\"\"\nEbookTranslator的命令行界面\n\"\"\"\n\nimport argparse\nimport sys\nimport os\nfrom pathlib import Path\nfrom"
},
{
"path": "EbookTranslator/EbookTranslator/convert2pdf.py",
"chars": 2471,
"preview": "import fitz\nimport os\n\n\ndef convert_to_pdf(input_file, output_file=None):\n \"\"\"\n 将支持的文档格式转换为 PDF,支持跨平台路径处理\n\n Arg"
},
{
"path": "EbookTranslator/EbookTranslator/load_config.py",
"chars": 2981,
"preview": "import os\nimport json\nimport requests\nfrom pathlib import Path\nfrom typing import Optional, Dict\n\n\ndef get_working_dir()"
},
{
"path": "EbookTranslator/EbookTranslator/main_function.py",
"chars": 20971,
"preview": "import math\nfrom . import All_Translation as at\nfrom PIL import Image\nimport pytesseract\nimport time\nimport fitz\nimport "
},
{
"path": "EbookTranslator/LICENSE",
"chars": 35149,
"preview": " GNU GENERAL PUBLIC LICENSE\n Version 3, 29 June 2007\n\n Copyright (C) 2007 Free "
},
{
"path": "EbookTranslator/README.md",
"chars": 11330,
"preview": "English | [简体中文](https://github.com/CBIhalsen/PolyglotPDF/blob/main//README_CN.md) | [繁體中文](https://github.com/CBIhalsen"
},
{
"path": "EbookTranslator/requirements.txt",
"chars": 123,
"preview": "deepl==1.17.0\nFlask\nflask-cors\nPillow==10.2.0\nPyMuPDF==1.24.0\npytesseract==0.3.10\nrequests==2.31.0\nWerkzeug==2.0.1\naioht"
},
{
"path": "EbookTranslator/setup.py",
"chars": 1941,
"preview": "from setuptools import setup, find_packages\n\nwith open(\"README.md\", \"r\", encoding=\"utf-8\") as fh:\n long_description ="
},
{
"path": "LICENSE",
"chars": 35149,
"preview": " GNU GENERAL PUBLIC LICENSE\n Version 3, 29 June 2007\n\n Copyright (C) 2007 Free "
},
{
"path": "LLMS_translation.py",
"chars": 25670,
"preview": "import asyncio\r\nimport aiohttp\r\nimport re\r\nimport requests\r\n\r\nimport load_config\r\n\r\nclass AI302_translation:\r\n def __"
},
{
"path": "OldMain.py",
"chars": 20299,
"preview": "import math\nimport All_Translation as at\nfrom PIL import Image\nimport pytesseract\nimport time\nimport fitz\nimport os\nimpo"
},
{
"path": "README.md",
"chars": 13427,
"preview": "python包在2.2版本之前预计不会更新,2.2版本预估采取解析最底层span获取更信息的布局逻辑解决,预估解决:行内公式错误判断为公式块,错误将粗体文本进行分段bug,以及insert_html方法重复嵌入字体文件导致处理页数较大pdf"
},
{
"path": "README_CN.md",
"chars": 7253,
"preview": "注: 对于pdf这种棘手的文件处理,对于文字版pdf的最优解:参考开源项目mupdf重构block识别算法只需要达到Adobe Acrobat Dc精度即可,不要舍近求远使用ocr扫描文字版pdf。 使用ai模型去理解pdf布局未来成本绝对"
},
{
"path": "README_JA.md",
"chars": 7711,
"preview": "# PolyglotPDF\n\n[](https://www.python.org/)\n[](https://www.python.org/)\n[](https://www.python.org/)\n[:\n "
},
{
"path": "app.py",
"chars": 19447,
"preview": "from flask import Flask, request, send_file, jsonify, send_from_directory\r\nimport json\r\nfrom pathlib import Path\r\n\r\nfrom"
},
{
"path": "build.py",
"chars": 10643,
"preview": "# import os\r\n# import sys\r\n# import platform\r\n# import subprocess\r\n# import shutil\r\n# from pathlib import Path\r\n#\r\n# def"
},
{
"path": "config.json",
"chars": 1287,
"preview": "{\r\n \"count\": 2,\r\n \"PPC\": 20,\r\n \"translation_services\": {\r\n \"AI302\": {\r\n \"auth_key\": \"\",\r\n \"model_name\": "
},
{
"path": "convert2pdf.py",
"chars": 2471,
"preview": "import fitz\nimport os\n\n\ndef convert_to_pdf(input_file, output_file=None):\n \"\"\"\n 将支持的文档格式转换为 PDF,支持跨平台路径处理\n\n Arg"
},
{
"path": "docker-compose.yml",
"chars": 407,
"preview": "version: '3'\nservices:\n polyglotpdf:\n image: 2207397265/polyglotpdf:latest\n ports:\n - \"12226:12226\"\n volu"
},
{
"path": "download_model.py",
"chars": 1986,
"preview": "import requests\nimport os\n\n\nsupport_language = [\n \"en\", # 英语 English\n \"zh\", # 中文 Chinese\n \"es\", # 西班牙语 Spani"
},
{
"path": "get_new_blocks.py",
"chars": 39107,
"preview": "#!/usr/bin/env python3\r\n# -*- coding: utf-8 -*-\r\n\r\nimport fitz # PyMuPDF 的库名是 fitz\r\nimport math\r\nimport datetime\r\nimpor"
},
{
"path": "index.html",
"chars": 16390,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"UTF-8\">\n <title>PolyglotPDF</title>\n <link rel=\"styles"
},
{
"path": "languagedetect.py",
"chars": 122,
"preview": "\n\ntext = \"今日は(こんにちは)\"\n\n# 方法2:直接使用detect\nfrom langdetect import detect\nlang_code = detect(text)\nprint(lang_code) # 输出: j"
},
{
"path": "load_config.py",
"chars": 14108,
"preview": "import json\r\nimport os\r\nimport sys\r\nimport time\r\nfrom typing import Any, Dict, List, Optional\r\nfrom pathlib import Path\r"
},
{
"path": "main.py",
"chars": 23638,
"preview": "import All_Translation as at\r\nfrom PIL import Image\r\nimport pytesseract\r\nimport time\r\nimport fitz\r\nimport os\r\n\r\nimport l"
},
{
"path": "merge_pdf.py",
"chars": 2471,
"preview": "import fitz\nimport os\n\ndef merge_pdfs_horizontally(pdf1_path, pdf2_path, output_path, spacing=0):\n \"\"\"\n 水平合并两个PDF文"
},
{
"path": "pdf_thumbnail.py",
"chars": 1432,
"preview": "import fitz\nimport os\n\n\ndef create_pdf_thumbnail(pdf_path, width=400):\n \"\"\"\n 为PDF文件第一页创建缩略图并保存到pdf_path上一层目录的thumb"
},
{
"path": "pdfviewer.html",
"chars": 8608,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width"
},
{
"path": "pdfviewer2.html",
"chars": 1576,
"preview": "\n\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width"
},
{
"path": "recent.json",
"chars": 201,
"preview": "[\r\n {\r\n \"index\": 0,\r\n \"date\": \"2025-04-13 01:34:09\",\r\n \"name\": \"2403.20127v1.pdf\",\r\n \"original_language\": \""
},
{
"path": "requirements.txt",
"chars": 157,
"preview": "deepl==1.17.0\nFlask==2.0.1\nflask-cors\nPillow==10.2.0\nPyMuPDF==1.24.0\npytesseract==0.3.10\nrequests==2.31.0\ntiktoken==0.6."
},
{
"path": "static/1.js",
"chars": 9638,
"preview": "\n // 在全局范围定义变量\n\n\n // 显示主页\n function showHome() {\n document.getElementById('recentread')."
},
{
"path": "static/2.js",
"chars": 1241,
"preview": "// 全局变量存储API密钥\nlet translationKeys = {\n AI302: '', \n deepl: '',\n google: '',\n youdao: '',\n aliyun: '',\n "
},
{
"path": "static/3.js",
"chars": 14241,
"preview": "let uploadFiles = new Map();\r\n// 文件输入处理\r\nconst fileInput = document.getElementById('fileInput');\r\n\r\nfunction triggerFile"
},
{
"path": "static/4.js",
"chars": 4274,
"preview": "\n// 用于存储批量选择的文章ID\nlet selectedBatchIds = new Set();\n\n// 展示批量管理弹窗\nfunction showBatchModal() {\n document.getElementById('"
},
{
"path": "static/i18n.js",
"chars": 24787,
"preview": "const i18n = {\r\nen: {\r\n // 页面中用到的 key: value\r\n '1': 'Library',\r\n '2': 'Hosmepage',\r\n '3': 'Recent Reading',\r"
},
{
"path": "static/main.css",
"chars": 14247,
"preview": "\n * {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n }\n\n body "
},
{
"path": "static/setup.css",
"chars": 4063,
"preview": "\n select.t-input {\n width: 300px;\n padding: 8px;\n border: 1px solid #ccc;\n border-radius: 4px;\n ba"
},
{
"path": "static/setup.js",
"chars": 15935,
"preview": "// 页面加载时获取配置\r\n fetch('/config_json')\r\n .then(response => response.json())\r\n .then(data => {\r\n "
},
{
"path": "static/thumbnail/...txt",
"chars": 0,
"preview": ""
},
{
"path": "update_recent.py",
"chars": 5119,
"preview": "import os\r\nimport json\r\nimport datetime\r\nfrom typing import List, Dict, Any\r\nimport glob\r\nfrom collections import Ordere"
}
]
About this extraction
This page contains the full source code of the CBIhalsen/PolyglotPDF GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 56 files (496.2 KB), approximately 133.3k tokens, and a symbol index with 272 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.