<Project-8.1 pdf2tx-MM> Python Flask 用浏览器翻译PDF内容 2个翻译引擎 繁简中文结果 从P8更改

news2024/11/27 2:34:19

更新

Project Name:pdf2tx (P6)
Date: 5oct.24
Function: 在浏览器中翻译PDF文件
Code:https://blog.csdn.net/davenian/article/details/142723144

升级
Project Name: pdf2tx-mm (P8)
7oct.24  
加入多线程,分页OCR识别,提高性能与速度
使用google translator,  Azure API 做为翻译机
使用NLTR 自然语言识别 多种西方文字 提高翻译质量
显示:处理时间, 翻译引擎,OCR识别的语言
Docker Folder: /app/pdf2tx-mm
Code:https://blog.csdn.net/davenian/article/details/142750333

升级 (P8.1)
8oct.24
使用jieba,可以对中文进行自然语言识别
对日文翻译,使用janome库,对日文使用自然语言分割,提高翻译准确
程序可以识别PDF是文本(langdetect),还是图片,图片才调用OCR
翻译过程并行化(ThreadPoolExecutor)
翻译结果加入 传统中文
可以下载翻译的文本
放弃ZhipuAI做为翻译机,因为在测试时,总是出发敏感词检测。
进度算法改为:考虑页数

已知问题:

代码调用的Google 翻译请求,在测试时用3语PDF文件(中+日+英),第一次翻译可以正常,但第二次(即使切换输出为不同语言)会有机会出现 1-5 次 “Request exception can happen due to an api connection error. Please check your connection and try again”,所以试着修改 max_length 从 5000 ,往下减 100 的值后,这个值也不能稳定在 4500 ,所以代码有了随机长度:“# 根据翻译引擎设置最大字符长度     if engine == 'google': max_length = random.randint(4200, 4700) else:  max_length = 5000” 这段。 至少5次测试后,能正常翻译。 正在看 RequestError · Issue #239 · nidhaloff/deep-translator · GitHub 提到的用 MyMemoryTranslator 加入到代码。  added on 9oct.24 719pm

代码

1. app.py

import os
import uuid
import logging
import configparser
from flask import Flask, render_template, request, redirect, url_for, Response
from threading import Thread, Lock
from werkzeug.utils import secure_filename
from pdf2image import convert_from_path
import pytesseract
from deep_translator import GoogleTranslator, MicrosoftTranslator
from concurrent.futures import ThreadPoolExecutor
from collections import defaultdict
import time # 导入 time 模块, 显示处理时间用
from datetime import timedelta #在结果页面显示处理时间,格式为 HH:MM
import nltk
#try:
#    nltk.data.find('tokenizers/punkt','tokenizers/punkt_tank')
#except LookupError:
#    nltk.download('punkt','punkt_tank', quiet=True)

#nltk.download('punkt', quiet=True) # 已经安装,用:python -m nltk.downloader all  
# 但运行还会报错! 还需要安装 unstructured 库,Y TMD在介绍里没说 f!
from functools import lru_cache
from pdfminer.high_level import extract_text as pdf_extract_text
from pdfminer.pdfparser import PDFSyntaxError
from langdetect import detect
import jieba
from janome.tokenizer import Tokenizer
import random


# 定义支持的语言映射
language_mapping = {
    'en': 'english',
    'fr': 'french',
    'de': 'german',
    'es': 'spanish',
    'it': 'italian',
    'ja': 'japanese',
    'ko': 'korean',
    'ru': 'russian',
    'zh-cn': 'chinese',
    'zh-tw': 'chinese',
    'zh': 'chinese',
    'pt': 'portuguese',
    'ar': 'arabic',
    'hi': 'hindi',
    # 添加其他语言
}


# OCR 语言代码映射
ocr_language_mapping = {
    'en': 'eng',
    'fr': 'fra',
    'de': 'deu',
    'es': 'spa',
    'it': 'ita',
    'ja': 'jpn',
    'ko': 'kor',
    'ru': 'rus',
    'zh-cn': 'chi_sim',
    'zh-tw': 'chi_tra',
    # 添加更多语言如有需要
}

# Microsoft Translator 语言代码映射
microsoft_language_mapping = {
    'en': 'en',
    'fr': 'fr',
    'de': 'de',
    'es': 'es',
    'it': 'it',
    'ja': 'ja',
    'ko': 'ko',
    'ru': 'ru',
    'zh-cn': 'zh-hans',
    'zh-tw': 'zh-hant',
    'pt': 'pt',
    'ar': 'ar',
    'hi': 'hi',
    # 添加更多语言如有需要
}

# Google Translator 语言代码映射
google_language_mapping = {
    'en': 'en',
    'fr': 'fr',
    'de': 'de',
    'es': 'es',
    'it': 'it',
    'ja': 'ja',
    'ko': 'ko',
    'ru': 'ru',
    'zh-cn': 'zh-CN',  # 修正为 Google 支持的简体中文代码
    'zh-tw': 'zh-TW',  # 修正为 Google 支持的繁体中文代码
    'zh': 'zh-CN',  # 默认简体中文
    'pt': 'pt',
    'ar': 'ar',
    'hi': 'hi',
    # 添加更多语言如有需要
}
 
# 初始化 Flask 应用
app = Flask(__name__)
app.config['ALLOWED_EXTENSIONS'] = {'pdf'}
app.config['UPLOAD_FOLDER'] = 'uploads'
app.config['MAX_CONTENT_LENGTH'] = 50 * 1024 * 1024  # 50MB
 
# 确保上传文件夹存在
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
 
# 全局变量
progress = defaultdict(int)
results = {}
progress_lock = Lock()
 
# 设置日志 格式
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
 
 
# 读取配置文件
config = configparser.ConfigParser()
config_file = 'config.ini'
 
if not os.path.exists(config_file):
    raise FileNotFoundError(f"配置文件 {config_file} 未找到,请确保其存在并包含必要的配置。")
 
config.read(config_file)
 
try:
    AZURE_API_KEY = config.get('translator', 'azure_api_key') # Microsoft Azure 需要KEY, 它给了2个,可以循环使用。用一个就行。
    AZURE_REGION = config.get('translator', 'azure_region') # 还需要 copied: This is the location (or region) of your resource. You may need to use this field when making calls to this API.
    # 如果有其他 API 密钥,例如 Yandex,可以在此添加
    # YANDEX_API_KEY = config.get('translator', 'yandex_api_key')
except (configparser.NoSectionError, configparser.NoOptionError):
    raise ValueError("配置文件中缺少必要的配置选项。")
 
# 允许的文件类型检查函数
def allowed_file(filename):
    return '.' in filename and filename.rsplit('.', 1)[1] in app.config['ALLOWED_EXTENSIONS']
 
# OCR 函数,指定语言
def ocr_image(image, lang='eng'):
    try:
        text = pytesseract.image_to_string(image, lang=lang)
    except Exception as e:
        logging.error(f"OCR 失败: {e}")
        text = ''
    return text

def chinese_sentence_split(text):
    # 使用 jieba 进行分词并辅助分句
    sentences = []
    current_sentence = []
    for word in jieba.cut(text):
        current_sentence.append(word)
        if word in ['。', '!', '?', ';']:
            sentence = ''.join(current_sentence).strip()
            if sentence:
                sentences.append(sentence)
            current_sentence = []
    if current_sentence:
        sentence = ''.join(current_sentence).strip()
        if sentence:
            sentences.append(sentence)
    return sentences

def japanese_sentence_split(text):
    # 使用 Janome 进行分词,并按标点符号分割
    tokenizer = Tokenizer()
    tokens = tokenizer.tokenize(text, wakati=True)
    sentences = []
    current_sentence = []
    for token in tokens:
        current_sentence.append(token)
        if token in ['。', '!', '?']:
            sentence = ''.join(current_sentence).strip()
            if sentence:
                sentences.append(sentence)
            current_sentence = []
    if current_sentence:
        sentence = ''.join(current_sentence).strip()
        if sentence:
            sentences.append(sentence)
    return sentences
 
# 翻译文本函数,支持分段、并行、进度更新、重试和缓存
# 翻译文本函数,支持分段、并行、进度更新、重试和缓存
def translate_text(text, engine, progress_callback=None, text_lang='en', target_language='en'):

    global google_language_mapping
    global microsoft_language_mapping

    logging.info(f"翻译引擎参数: {engine}")

    # 句子分割
    nltk_lang = language_mapping.get(text_lang, 'english')
    if nltk_lang in ['english', 'french', 'german', 'spanish', 'italian', 'russian']:
        try:
            sentences = nltk.sent_tokenize(text, language=nltk_lang)
        except Exception as e:
            logging.error(f"NLTK 分句失败,使用默认分割方法:{e}")
            sentences = text.split('\n')
    elif nltk_lang == 'chinese':
        sentences = chinese_sentence_split(text)
    elif nltk_lang == 'japanese':
        sentences = japanese_sentence_split(text)
    else:
        sentences = text.split('\n')

        # 根据翻译引擎设置最大字符长度
    if engine == 'google':
        max_length = random.randint(4200, 4700)
    else: 
        max_length = 5000


    # 确保 target_language 已被正确设置
    if not target_language:
        logging.error("未能正确设置目标语言,使用默认值 'en'")
        target_language = 'en'

    # 初始化翻译器
    translator = None
    if engine == 'google':
        target_language = google_language_mapping.get(target_language, 'en')  # 使用正确的目标语言
        translator = GoogleTranslator(source='auto', target=target_language)
        logging.info(f"初始化翻译器, google Target_language: {target_language}")
    elif engine == 'microsoft':
        # 使用用户提供的目标语言代码进行翻译
        source_language = microsoft_language_mapping.get(text_lang, 'en')
        target_language = microsoft_language_mapping.get(target_language, 'en')
        logging.info(f"初始化翻译器, Azure Source Language: {source_language}, Target Language: {target_language}")
        translator = MicrosoftTranslator(
            source=source_language,
            target=target_language,
            api_key=AZURE_API_KEY,
            region=AZURE_REGION
        )

    # 将句子组合成不超过最大长度的块
    chunks = []
    current_chunk = ''
    for sentence in sentences:
        if len(current_chunk) + len(sentence) + 1 <= max_length:
            current_chunk += sentence + ' '
        else:
            chunks.append(current_chunk.strip())
            current_chunk = sentence + ' '
    if current_chunk:
        chunks.append(current_chunk.strip())

    translated_chunks = [''] * len(chunks)
    total_chunks = len(chunks)
    completed_chunks = 0

    # 定义翻译单个块的函数,带有重试机制
    def translate_chunk(index, chunk):
        nonlocal completed_chunks
        max_retries = 3
        for attempt in range(max_retries):
            try:
                translated_chunk = translator.translate(chunk)
                translated_chunks[index] = translated_chunk
                break  # 成功后跳出循环
            except Exception as e:
                logging.error(f"翻译块 {index} 失败,尝试次数 {attempt + 1}: {e}")
                if attempt == max_retries - 1:
                    translated_chunks[index] = chunk  # 最后一次重试失败,使用原文
        completed_chunks += 1
        if progress_callback:
            progress = int(100 * completed_chunks / total_chunks)
            progress_callback(progress)

    # 使用线程池并行翻译
    with ThreadPoolExecutor(max_workers=5) as executor:
        for idx, chunk in enumerate(chunks):
            executor.submit(translate_chunk, idx, chunk)

    # 重建翻译后的文本
    translated_text = ' '.join(translated_chunks)
    return translated_text.strip()



 
# 后台处理函数 
# 使用 logging.info 在调试模式中输出所使用的翻译引擎和处理时间 
# 在任务开始时,记录开始时间 start_time。
# 在任务结束时,记录结束时间 end_time,计算处理时间 elapsed_time。
# 将 elapsed_time 保存到 results 字典中,以便在结果页面显示
# 加入对pdf file checking. 如果不是Image,跳过OCR. 9oct.24 1230am
def process_file(task_id, filepath, engine, ocr_language, target_language):

    global results
    global language_mapping  # 声明使用全局变量
    try:
        start_time = time.time()  # 记录开始时间

        logging.info(f"任务 {task_id}: 开始处理文件 {filepath},使用 OCR 语言 {ocr_language},翻译引擎 {engine}, 目标语言 {target_language}"),  # 输出详细信息

        with progress_lock:
            progress[task_id] = 0

        # 尝试直接提取文本
        extracted_text = ''
        try:
            extracted_text = pdf_extract_text(filepath)
            if extracted_text.strip():
                logging.info(f"任务 {task_id}: 成功提取文本,无需 OCR")
                with progress_lock:
                    progress[task_id] = 50  # 文本提取完成,进度更新为 50%

                # 在提取文本后,检测语言
                try:
                    detected_language = detect(extracted_text)
                    logging.info(f"检测到的文本语言:{detected_language}")
                    if detected_language not in language_mapping:
                        logging.warning(f"检测到的语言 '{detected_language}' 不在支持的语言列表中,使用默认语言 'en'")
                        detected_language = 'en'
                except Exception as e:
                    logging.error(f"语言检测失败,使用默认语言 'en'。错误信息:{e}")
                    detected_language = 'en'

            else:
                logging.info(f"任务 {task_id}: 提取到的文本为空,使用 OCR 处理")
                raise ValueError("Empty text extracted")
        except Exception as e:  # 如果直接提取文本失败,使用 OCR 处理
            logging.info(f"任务 {task_id}: 无法直接提取文本,将使用 OCR 处理。原因:{e}")
            # 将 PDF 转换为图像
            images = convert_from_path(filepath)

            total_pages = len(images)
            total_steps = total_pages

            extracted_text = ''
            for i, image in enumerate(images):
                text = ocr_image(image, lang=ocr_language_mapping.get(ocr_language,'eng'))
                
                extracted_text += text + '\n'
                with progress_lock:
                    progress[task_id] = int(100 * (i + 1) / total_steps * 0.5)  # OCR 占 50% 进度
            with progress_lock:
                progress[task_id] = 50  # OCR 完成,进度更新为 50%

            # 在 OCR 提取后,检测语言
            try:
                detected_language = detect(extracted_text)
                logging.info(f"检测到的文本语言:{detected_language}")
                if detected_language not in language_mapping:
                    logging.warning(f"检测到的语言 '{detected_language}' 不在支持的语言列表中,使用默认语言 'en'")
                    detected_language = 'en'
            except Exception as e:
                logging.error(f"语言检测失败,使用默认语言 'en'。错误信息:{e}")
                detected_language = 'en'

        # 翻译文本,传递 progress_callback
        def progress_callback(p):
            with progress_lock:
                progress[task_id] = 50 + int(p * 0.5)  # 翻译占 50% 进度

        # 将检测到的语言传递给 translate_text 函数,并确保 engine 是小写
        translated_text = translate_text(extracted_text, engine, progress_callback, detected_language, target_language)


        with progress_lock:
            progress[task_id] = 100

        # 计算处理时间
        end_time = time.time()
        elapsed_time = end_time - start_time  # 处理所用的时间,单位为秒

        # 将处理时间保存到结果中
        result = {
            'original': extracted_text,
            'translated': translated_text,
            'elapsed_time': elapsed_time,  # 添加处理时间
            'engine': engine,           # 添加翻译引擎
            'ocr_language': ocr_language,        # 添加 OCR 语言
            'target_language': target_language
        }
        results[task_id] = result

        # 删除上传的文件
        os.remove(filepath)

        logging.info(f"任务 {task_id}: 处理完成,耗时 {elapsed_time:.2f} 秒")  # 输出处理时间

    except Exception as e:
        logging.error(f"处理失败: {e}")
        with progress_lock:
            progress[task_id] = -1
    finally:
        # 确保上传的文件被删除,即使出现异常
        if os.path.exists(filepath):
            os.remove(filepath)
        logging.info(f"任务 {task_id}: 文件已删除")



 
# 文件上传路由
@app.route('/', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        # 检查请求中是否有文件
        if 'file' not in request.files:
            return '请求中没有文件部分', 400
        file = request.files['file']
        if file.filename == '':
            return '未选择文件', 400
        if file and allowed_file(file.filename):
            # 安全地保存文件
            filename = secure_filename(f"{uuid.uuid4().hex}_{file.filename}")
            filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
            file.save(filepath)
 
            # 获取选择的翻译引擎和 OCR 语言,设置默认值
            engine = request.form.get('engine', 'google')
            ocr_language = request.form.get('ocr_language', 'en')
            target_language = request.form.get('target_language', 'zh-cn')
 
            # 创建唯一的任务 ID
            task_id = str(uuid.uuid4())
            progress[task_id] = 0
 
            # 启动后台处理线程
            thread = Thread(target=process_file, args=(task_id, filepath, engine, ocr_language, target_language))
            thread.start()
 
            # 重定向到进度页面
            return redirect(url_for('processing', task_id=task_id))
        else:
            return '文件类型不被允许', 400
    return render_template('upload.html')
 
# 处理页面路由
@app.route('/processing/<task_id>')
def processing(task_id):
    return render_template('processing.html', task_id=task_id)
 
# 进度更新路由
@app.route('/progress/<task_id>')
def progress_status(task_id):
    def generate():
        while True:
            with progress_lock:
                status = progress.get(task_id, 0)
            yield f"data: {status}\n\n"
            if status >= 100 or status == -1:
                break
    return Response(generate(), mimetype='text/event-stream')
 
# 结果页面路由
@app.route('/result/<task_id>')
def result(task_id):
    result_data = results.get(task_id)
    if not result_data:
        return '结果未找到', 404
 
    # 获取处理时间
    elapsed_time = result_data.get('elapsed_time', 0)
    # 将处理时间格式化为 HH:MM:SS
    elapsed_time_str = str(timedelta(seconds=int(elapsed_time)))
 
    return render_template(
        'result.html', 
        original=result_data['original'], 
        translated=result_data['translated'], 
        elapsed_time=elapsed_time_str,
        engine=result_data['engine'],
        ocr_language=result_data['ocr_language']
    )
 
if __name__ == '__main__':
    app.run(host='0.0.0.0', port=9006, debug=True)

2. upload.html

<!-- templates/upload.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>PDF翻译器</title>
</head>
<body>
    <h1>上传PDF文件进行翻译</h1>
    <form action="{{ url_for('upload_file') }}" method="post" enctype="multipart/form-data">
        <div>
            <label for="file">选择PDF文件:</label>
            <input type="file" id="file" name="file" accept=".pdf" required>
        </div>
        <div>
            <label for="ocr_language">选择OCR语言:</label>
            <select id="ocr_language" name="ocr_language">
                <option value="en">英语</option>
                <option value="fr">法语</option>
                <option value="de">德语</option>
                <option value="es">西班牙语</option>
                <option value="it">意大利语</option>
                <option value="ja">日语</option>
                <option value="ko">韩语</option>
                <option value="ru">俄语</option>
                <option value="zh-cn">简体中文</option>
                <option value="zh-tw">繁体中文</option>
                <!-- 如需更多语言,请在此添加 -->
            </select>
        </div>
        <div>
            <label for="engine">选择翻译引擎:</label>
            <select id="engine" name="engine">
                <option value="google">Google 翻译</option>
                <option value="microsoft">Microsoft 翻译</option>
                <!-- 如有其他翻译引擎,可在此添加 -->
            </select>
            <label for="target_language">选择目标语言:</label>
            <select id="target_language" name="target_language">
                <option value="zh-cn">简体中文</option>
                <option value="zh-tw">繁体中文(台湾)</option>
                <!-- 其他语言选项 -->
            </select>
        </div>
        <div>
            <button type="submit">开始翻译</button>
        </div>
    </form>
</body>
</html>

3. processing.html

<!-- templates/processing.html -->

<!doctype html>
<html>
<head>
    <title>处理中...</title>
    <style>
        #progress-bar {
            width: 50%;
            background-color: #f3f3f3;
            margin: 20px 0;
        }
        #progress-bar-fill {
            height: 30px;
            width: 0%;
            background-color: #4caf50;
            text-align: center;
            line-height: 30px;
            color: white;
        }
    </style>
</head>
<body>
    <h1>文件正在处理中,请稍候...</h1>
    <div id="progress-bar">
        <div id="progress-bar-fill">0%</div>
    </div>
    <script>
        var taskId = "{{ task_id }}";
        var progressBarFill = document.getElementById('progress-bar-fill');

        var eventSource = new EventSource('/progress/' + taskId);
        eventSource.onmessage = function(event) {
            var progress = event.data;
            if (progress == '-1') {
                <!-- alert('处理失败,请重试。'); -->
                eventSource.close();
                window.location.href = '/';
            } else {
                progressBarFill.style.width = progress + '%';
                progressBarFill.innerText = progress + '%';
                if (progress >= 100) {
                    eventSource.close();
                    window.location.href = '/result/' + taskId;
                }
            }
        };
    </script>
</body>
</html>

4. result.html

<!-- templates/result.html -->
<!doctype html>
<html>
<head>
    <title>翻译结果</title>
    <style>
        .container {
            display: flex;
        }
        .content {
            width: 50%;
            padding: 20px;
            box-sizing: border-box;
            overflow-y: scroll;
            height: 80vh;  /* 调整高度,给处理时间留出空间 */
        }
        .original {
            background-color: #f9f9f9;
        }
        .translated {
            background-color: #eef9f1;
        }
        pre {
            white-space: pre-wrap;
            word-wrap: break-word;
        }
    </style>
</head>
<body>
    <h1>翻译结果</h1>
    <p>处理时间:{{ elapsed_time }}</p>  <!-- 显示处理时间 -->
    <p>使用的翻译引擎:{{ engine|capitalize }}</p>   <!-- 显示翻译引擎 , 使用capitalize过滤器 首字母大字-->
    <p>OCR 语言:{{ ocr_language }}</p>      <!-- 显示OCR 语言 -->
    <!-- CHANGE: 添加下载译文的功能 -->
    <button onclick="downloadTranslatedText()">下载译文</button>
    <button onclick="window.location.href='/'">返回主页</button>
    <div class="container">
        <div class="content original">
            <h2>原文</h2>
            <pre>{{ original }}</pre>
        </div>
        <div class="content translated">
            <h2>译文</h2>
            <pre>{{ translated }}</pre>
        </div>
    </div>
    <script>
        function downloadTranslatedText() {
            var element = document.createElement('a');
            var text = `{{ translated|e }}`;
            var file = new Blob([text], {type: 'text/plain'});
            element.href = URL.createObjectURL(file);
            element.download = 'translated.txt';
            document.body.appendChild(element);
            element.click();
            document.body.removeChild(element);
        }
    </script>
</body>
</html>

5. config.ini

[translator]
azure_api_key = 5abb1ab..
azure_region = south..
mymemorytranslator_key = 4ba808c..
email_address = dave3.nian@gmail.com
openai_api_key = sk-proj..9KrfsMyI30Am3..
#yandex_api_key = YOUR_YANDEX_API_KEY
zhipu_api_key = 23358bf...

6. Dockerfile

# 使用官方的 Python 3.12.3 slim 版本作为基础镜像
FROM python:3.12.3-slim

# 设置环境变量
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1


# 设置工作目录   #从P8开始,项目文件在container中位置: /app/<project name>
WORKDIR /app/pdf2tx-mm

# 复制应用程序代码到容器中  #从P8开始,项目文件在container中位置: /app/<project name>
COPY . /app/pdf2tx-mm

# 升级 pip
RUN pip install --upgrade pip

# 安装系统依赖项
RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential \
    tesseract-ocr \
    libtesseract-dev \
    poppler-utils \
    libglib2.0-0 \
    && rm -rf /var/lib/apt/lists/*

# 如果需要特定的 Tesseract 语言包,中文 日文
RUN apt-get update && apt-get install -y --no-install-recommends \
    tesseract-ocr-chi-sim \
    tesseract-ocr-chi-tra \
    tesseract-ocr-jpn\
    && rm -rf /var/lib/apt/lists/*

# 安装 Python 依赖项
RUN pip install --no-cache-dir -r requirements.txt

# 下载 NLTK 数据
RUN python -m nltk.downloader all

# Copy the rest of the application code
COPY . /app/

# 暴露应用程序运行的端口
EXPOSE 9006

# 设置环境变量以指定Flask运行的主机和端口
ENV FLASK_RUN_HOST=0.0.0.0
ENV FLASK_RUN_PORT=9006

# 运行应用程序
CMD ["python", "app.py"]

7. requirements.txt

Flask
pdf2image
pytesseract
deep_translator
nltk
pdfminer.six
langdetect
jieba
janome
werkzeug
gunicorn

Docker deployment:

 docker build -t pdf2tx-mm.8.1 .
docker run -d -p 9006:9006 --name pdf2tx-mm.8.1_container pdf2tx-mm.8.1

注: 第一条命令是,创建一个image: pdf2tx-mm.8.1
         命令二是: 创建一个来自pdf2tx-mm.8.1 镜像的容器,容器名字是: pdf2tx-mm.8.1_container 

演示

这个PDF是3语的,翻译能看。

如果在windows上运行,看P8安装指导。

Linux docker部署,可直接使用命令,看本文章

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2204669.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

美发店管理革新:SpringBoot系统的应用

1系统概述 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及&#xff0c;互联网成为人们查找信息的重要场所&#xff0c;二十一世纪是信息的时代&#xff0c;所以信息的管理显得特别重要。因此&#xff0c;使用计算机来管理美发门店管理系统的相关信息成为必然。开发…

3D生成基础模型来了!只需5秒,高质量3D资产规模化生成!南洋理工等重磅开源3DTopia-XL

文章链接&#xff1a;https://arxiv.org/pdf/2409.12957 项目链接&#xff1a;https://3dtopia.github.io/3DTopia-XL/ 今天AI生成未来和大家分享的是南洋理工、北大、上海AI Lab和港中文联合发布的3D PBR资产生成最新工作3DTopia-XL。通过基于高效且表达力强的3D表示方法Pri…

Vue3 集成Monaco Editor编辑器

Vue3 集成Monaco Editor编辑器 1. 安装依赖2. 使用3. 效果 Monaco Editor &#xff08;官方链接 https://microsoft.github.io/monaco-editor/&#xff09;是一个由微软开发的功能强大的在线代码编辑器&#xff0c;被广泛应用于各种 Web 开发场景中。以下是对 Monaco Editor 的…

【linux 多进程并发】0201 Linux进程fork内存空间,父子进程变量内存地址居然是一样的

0201 Linux进程fork方式详解 ​专栏内容&#xff1a; postgresql使用入门基础手写数据库toadb并发编程 个人主页&#xff1a;我的主页 管理社区&#xff1a;开源数据库 座右铭&#xff1a;天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物. 文章…

学习记录:js算法(五十七):二叉树中所有距离为 K 的结点

文章目录 二叉树中所有距离为 K 的结点思路一思路二 二叉树中所有距离为 K 的结点 给定一个二叉树&#xff08;具有根结点 root&#xff09;&#xff0c; 一个目标结点 target &#xff0c;和一个整数值 k &#xff0c;返回到目标结点 target 距离为 k 的所有结点的值的数组。&…

matlab002

新建工程test001 例如&#xff1a; 脚本&#xff08;Script&#xff09; 概念 脚本是一系列按顺序执行的 MATLAB 命令的集合。它就像是一个记录了你在命令行中输入的一系列指令的文件。用途 适用于简单的任务&#xff0c;例如数据处理、可视化等一次性的操作。例如&#xff0c…

重学SpringBoot3-集成Redis(四)之Redisson

更多SpringBoot3内容请关注我的专栏&#xff1a;《SpringBoot3》 期待您的点赞&#x1f44d;收藏⭐评论✍ 重学SpringBoot3-集成Redis&#xff08;四&#xff09;之Redisson 1. 添加 Redisson 依赖2. 配置 Redisson 客户端3. 使用 Redisson 实现分布式锁4. 调用分布式锁5. 为什…

Java 获取热搜并生成图片

效果图如下&#xff1a; 第一步获取热搜 public List<String> getHotNews4(Integer size) {if (size < 0 || StringUtils.isEmpty(size)) {return null;}try {//set 转listreturn new ArrayList<>(getHotNews(size));} catch (Exception e) {logger.error(&qu…

如何基于审批实现文件外发管控,阻断数据违规外流?

FTP可以说是实际中企业运用最广泛的文件传输方式&#xff0c;很多企业不仅内部传输文件使用FTP&#xff0c;在与外部合作伙伴协作时&#xff0c;也多采用FTP进行文件的外发和收取。例如半导体行业&#xff0c;默认的都是使用FTP进行文件外发&#xff0c;这时候&#xff0c;替换…

卷积神经网络细节问题及知识点

一、Batch Normalization Batch Normalization&#xff08;BN&#xff0c;批归一化&#xff09; 是深度学习中的一种技术&#xff0c;主要用于加速神经网络的训练过程&#xff0c;同时提高网络的稳定性和收敛速度。它通过对每一层的输出进行归一化&#xff0c;减少梯度消失和梯…

本地部署Docsify生成文档网站并实现公网环境远程访问

文章目录 前言1. 本地部署Docsify2. 使用Docsify搭建个人博客3. 安装Cpolar内网穿透工具4. 配置公网地址5. 配置固定公网地址 前言 本文主要介绍如何在Windows环境本地部署 Docsify 这款以 markdown 为中心的文档编辑器&#xff0c;并即时生成您的文档博客网站&#xff0c;结合…

ubuntu22.04 安装wine9.0 全网首发

wine官网推荐安装方式&#xff1a;https://gitlab.winehq.org/wine/wine/-/wikis/zh_CN/Debian-Ubuntu 博主按照这种方式是失败的&#xff0c;虽然开启了“低调上网”&#xff0c;貌似代理对于终端不起作用&#xff0c;后面会介绍替代方案&#xff0c;一样完美。 一、官网的安…

Pycharm里设置关于designer.exe以及pyuic5.exe的外部工具

文章目录 1.Pycharm与Pyuic5介绍(1)Pycharm(2)Pyuic5 2.Pycharm里设置外部工具(1)切换到外部工具(2)designer创建外部工具(3)pyuic5创建外部工具(4)使用designer和pyuic5 3.本章总结 1.Pycharm与Pyuic5介绍 (1)Pycharm Pycharm是专门用于python编程语言的编辑软件&#xff0c;…

QT的核心机制 对话框资源

案例 1、键盘按下w&#xff0c;s&#xff0c;a&#xff0c;d键分别为标签向上&#xff0c;下&#xff0c;左&#xff0c;右移动 鼠标按下获取本地坐标&#xff0c;全局坐标 鼠标双击获取本地坐标&#xff0c;全局坐标 鼠标移动获取本地坐标&#xff0c;全局坐标 让鼠标跟踪…

C语言 ——— oj题:有效的括号

目录 题目要求 代码实现 题目要求 给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串 s &#xff0c;判断字符串是否有效 有效字符串需满足&#xff1a; 左括号必须用相同类型的右括号闭合。左括号必须以正确的顺序闭合。每个…

【靶点Talk】为什么联合用药喜欢用VEGF+VEGFR?

血管生成对肿瘤发生、发展的重要影响的发现&#xff0c;使肿管生成变为肿瘤研究的热点之一。今天给大家带来VEGF、VEGFR相关介绍&#xff0c;更多靶点科普视频请关注义翘神州B站和知乎官方账号。 1、VEGFR的“简历” VEGFR包括VEGFR-1、VEGFR-2和VEGFR-3。VEGFR-2可与多种VEGF…

leetcode hot100_part03_滑动窗口

滑动窗口是有一个基本的模版的&#xff0c;不要自己想当然哦~ 滑动窗口算法思想&#xff08;附经典例题&#xff09;_滑动窗口的思想-CSDN博客 滑动窗口也叫同向双指针&#xff1b;可以先看一下灵山视频&#xff1a;滑动窗口【基础算法精讲 03】_哔哩哔哩_bilibili 3.无重复字…

springboot如何自动生成mybatis映射文件、dao、pojo层文件?

背景&#xff1a;以前一直是直接cv一个项目中现成的xml文件&#xff0c;然后再去自己配置mapper等数据。自己准备做一个单独的例子试一下。 步骤1&#xff1a;在pom.xml文件中插入mybatis-generator插件&#xff0c;这里选的版本是1.3.2&#xff0c;然后指定的generator文件是在…

ChatGPT 4o with Canvas — 新特性详解

# ChatGPT 4o with Canvas — 新特性详解 最近猫哥也感受到 Canvas 的强大&#xff0c;顺手开了个会员体验了一天&#xff0c;今天给大家简单分享一下&#xff0c;有想体验的伙伴可以文末名片私信我哈&#xff01; 关键词&#xff1a; #ChatGPT4o #Canvas新特性 #AI写作工具 …

[论文笔记]DAPR: A Benchmark on Document-Aware Passage Retrieval

引言 今天带来论文DAPR: A Benchmark on Document-Aware Passage Retrieval的笔记。 本文提出了一个基准&#xff1a;文档感知段落检索(Document-Aware Passage Retrieval,DAPR)以及介绍了一些上下文段落表示的方法。 为了简单&#xff0c;下文中以翻译的口吻记录&#xff0c…