当前位置: 首页 > news >正文

<Project-6 pdf2tx> Python Flask 应用:图片PDF图书的中文翻译解决方案

重要更新!

Modified on 8oct24.   
P6已经被 P8 替代,后着支持多任务,多翻译机。在速度与资源占用上,都好于这个P6。 
新的 P8 文章链接:

 <Project-8 pdf2tx-MM> Python Flask应用:在浏览器中翻译PDF文件 NLTK OCR 多线程 指定翻译器 改进后的P6

目的

       能够从扫描的 PDF 图片中提取文本,并将其翻译成中文后生成对应的译文。

起因

看维基百科:https://en.wikipedia.org/wiki/Soviet%E2%80%93Afghan_War 苏联阿富汗战争。里面提到中国对阿富汗圣战者(当时反苏联,反傀儡阿富汗政府【阿富汗民主共和国】入侵的阿富汗民兵游机队)给予军事上的援助,当时对圣战者主要援助国:巴基斯坦、美国、英国、中国、伊朗和波斯湾阿拉伯国家。想得到更多的内容,就看到一本书:《Xinjiang: China's Muslim Borderland》新疆:中国的穆斯林边疆 ,这书$18,然后... 每页都是图片,还是英文的,一个字也看不懂。只能让电脑去翻译,还能拼出这个文章。

下面有些是复制Chatgpt回答内容:根据代码内容,写一篇这个程序的介绍,项目名称 pdf2tx,但是能用的不多,看到还要提高prompt表述能力。

pdf2tx 是一个旨在将图片格式的 PDF 图书自动翻译成中文的项目,尤其适合需要将外文书籍或文档快速翻译为中文的用户。该工具结合了 OCR(光学字符识别)技术与机器翻译,能够从扫描的 PDF 图片中提取文本,并将其翻译成中文后生成对应的译文。

功能

  • 上传 PDF 文件:用户通过网页界面上传 PDF 文件,上传页面(upload.html)提供了简单的表单,支持 .pdf 文件的选择和提交​(upload)。

  • 进度显示:上传后,应用程序会进入处理状态,并通过进度条(processing.html)显示处理进度。使用了 Socket.IO 来实时更新处理进度,增强用户体验​(processing)。

  • 文本提取与翻译:程序使用 pdf2image 将 PDF 转换为图像,然后利用 pytesseract 进行 OCR(光学字符识别)以提取文字内容。随后,通过 deep_translator 库将提取的文字翻译为目标语言​(requirements)。

  • 翻译结果展示:处理完成后,结果页面(result.html)展示原文和译文。文本以双栏布局展示,方便对比查看​(result)。

  1. 上传 PDF 文件: 用户需要通过网页界面上传需要翻译的 PDF 文件。可以访问 upload.html 页面,选择 PDF 文件并点击上传按钮。

  2. 翻译过程: 文件上传后,系统会开始处理,显示处理进度。用户可以通过网页界面查看 OCR 与翻译的实时进展。

  3. 查看翻译结果: 翻译完成后,用户可以查看原文与译文的对比结果。网页会将翻译后的内容展示在左右两个部分,方便用户对照查看。

只下存了5页用于测试程序。

程序的逻辑

PDF转为图像
   -> OCR提取图片中的文字
           -> 文字交给翻译器
                       -> 翻译的结果在网页上显示

因为使用时,可能会用到未授权问题,上传代码没有保存内容的功能。

代码内容介绍

pdf2tx 的代码结构由以下几个关键部分组成:

  1. 主应用程序 (app.py)app.py 是整个项目的核心,用于处理用户请求、管理前端页面以及实现 OCR 和翻译功能。该程序基于 Flask 框架,提供了简单的 Web 服务,同时使用 flask_socketio 实现前端与后端之间的实时通信

  2. OCR 与翻译功能: OCR 和翻译功能是 pdf2tx 的核心逻辑。它们通过 pdf2image 将 PDF 页面转换为图像,然后使用 pytesseract 提取文本,再通过 deep_translator 将提取的文本翻译为中文。

  3. 前端 HTML 模板

    • upload.html:用户可以上传 PDF 文件进行翻译。

    • processing.html:显示文件处理进度的页面,包含一个进度条,实时显示 OCR 和翻译的进度。

    • result.html:展示原文和译文的页面,用户可以对比查看翻译结果。

    前端页面与后端通过 SocketIO 实现实时交互,确保用户可以直观地看到文件处理进度和最终的翻译结果。

完整代码

目录结构

pdf2tx/
├── app.py                    # 主程序文件,包含应用程序的逻辑
├── Dockerfile                # Docker 配置文件,用于容器化部署
├── requirements.txt          # Python 依赖文件,列出所需安装的包
├── templates/                # HTML 模板文件夹
        ├─ upload.html           # 上传页面
        ├── processing.html       # 处理中页面
        └── result.html           # 结果展示页面

目录结构说明

  1. app.py:包含应用程序的核心逻辑,例如路由定义、文件处理、文字提取、翻译等。
  2. Dockerfile:用于定义项目的 Docker 镜像,方便部署和环境管理。
  3. requirements.txt:列出了项目所需的 Python 库,如 Flask、pdf2imagepytesseract 等​(requirements)。
  4. templates/:包含应用程序的 HTML 模板文件,用于与用户交互的网页。
    • upload.html:用于文件上传的页面​(upload)。
    • processing.html:用于显示文件处理进度的页面​(processing)。
    • result.html:用于展示翻译结果的页面​(result)。

代码

app.py

import eventlet
eventlet.monkey_patch()import os
import uuid
from flask import Flask, render_template, request, redirect, url_for
from flask_socketio import SocketIO
from pdf2image import convert_from_path
import pytesseract
from deep_translator import GoogleTranslator
from werkzeug.utils import secure_filename
import logging# 初始化日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)app = Flask(__name__)
app.config['ALLOWED_EXTENSIONS'] = {'pdf'}socketio = SocketIO(app, cors_allowed_origins="*")# 获取 app.py 文件所在的目录
current_directory = os.path.dirname(os.path.abspath(__file__))# 设置 uploads_FOLDER 为 'uploads' 目录的绝对路径
uploads_directory = os.path.join(current_directory, 'uploads')
app.config['UPLOAD_FOLDER'] = uploads_directory
app.config['MAX_CONTENT_LENGTH'] = 50 * 1024 * 1024  # 50MB# 切换当前工作目录到项目目录
os.makedirs(uploads_directory, exist_ok=True)# 创建上传目录(如果不存在)
if not os.path.exists(app.config['UPLOAD_FOLDER']):os.makedirs(app.config['UPLOAD_FOLDER'])# 允许的文件扩展名检查函数
def allowed_file(filename):return '.' in filename and \filename.rsplit('.', 1)[1].lower() in app.config['ALLOWED_EXTENSIONS']# 翻译函数
def translate_text(text):max_length = 5000  # Google Translate 单次请求的最大字符数translated_text = ''paragraphs = text.split('\n')temp_text = ''for para in paragraphs:if len(temp_text) + len(para) < max_length:temp_text += para + '\n'else:try:translated = GoogleTranslator(source='auto', target='zh-CN').translate(text=temp_text)except Exception as e:print(f"翻译失败:{e}")translated = temp_text  # 如果翻译失败,使用原文本translated_text += translated + '\n'temp_text = para + '\n'if temp_text:try:translated = GoogleTranslator(source='auto', target='zh-CN').translate(text=temp_text)except Exception as e:print(f"翻译失败:{e}")translated = temp_text  # 如果翻译失败,使用原文本translated_text += translated + '\n'return translated_text
#注:可以尝试使用DeepL, 百度 有免费期,或chatgpt gemini 就是比较贵# PDF 转换为图像函数
def pdf_to_images(filepath):try:images = convert_from_path(filepath)except Exception as e:logger.error(f"PDF 转换失败:{e}")raise ereturn images@app.route('/', methods=['GET', 'POST'])
def upload_file():if request.method == 'POST':# 检查文件是否在请求中if 'file' not in request.files:return '请求中没有文件部分'file = request.files['file']if file.filename == '':return '未选择文件'if file and allowed_file(file.filename):# 使用唯一且安全的文件名使用UUIDext = os.path.splitext(file.filename)[1]filename = f"{uuid.uuid4().hex}{ext}"filename = secure_filename(filename)filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)try:file.save(filepath)except Exception as e:return f"文件保存失败:{e}"# 重定向到处理页面,文件名作为查询参数return redirect(url_for('processing', filename=filename))return render_template('upload.html')@app.route('/processing')
def processing():filename = request.args.get('filename', None)if not filename:return '文件未找到'return render_template('processing.html', filename=filename)@app.route('/result')
def result():return render_template('result.html')@socketio.on('connect')
def connect():logger.info('客户端已连接')@socketio.on('start_processing')
def start_processing(data):sid = request.sidsocketio.start_background_task(process_file, data, sid)def process_file(data, sid):filename = data.get('filename', None)if not filename:socketio.emit('error', {'data': '文件未找到'}, room=sid)returnfilepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)if not os.path.exists(filepath):socketio.emit('error', {'data': '文件未找到'}, room=sid)returnsocketio.emit('progress', {'data': 10}, room=sid)try:images = pdf_to_images(filepath)except Exception as e:socketio.emit('error', {'data': f'PDF 转换失败:{e}'}, room=sid)returnsocketio.emit('progress', {'data': 30}, room=sid)extracted_text = ''total_pages = len(images)for i, image in enumerate(images):try:text = pytesseract.image_to_string(image)except Exception as e:socketio.emit('error', {'data': f'OCR 识别失败:{e}'}, room=sid)returnextracted_text += text + '\n'progress = 30 + int(50 * (i + 1) / total_pages)socketio.emit('progress', {'data': progress}, room=sid)try:translated_text = translate_text(extracted_text)except Exception as e:socketio.emit('error', {'data': f'翻译失败:{e}'}, room=sid)returnsocketio.emit('progress', {'data': 100}, room=sid)socketio.emit('result', {'original': extracted_text, 'translated': translated_text}, room=sid)try:os.remove(filepath)except Exception as e:logger.error(f"删除文件失败:{e}")if __name__ == '__main__':socketio.run(app, host='0.0.0.0', port=9004, debug=True)

upload.html

<!-- templates/upload.html --><!doctype html>
<html>
<head><title>上传 PDF 文件</title>
</head>
<body><h1>上传 PDF 文件以进行翻译</h1><form method="post" enctype="multipart/form-data"><input type="file" name="file" accept=".pdf" required><input type="submit" value="上传并开始处理"></form>
</body>
</html>

processing.html

<!doctype html>
<html>
<head><title>处理中...</title><script src="https://cdn.socket.io/4.0.0/socket.io.min.js"></script><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 filename = "{{ filename }}";var socket = io();socket.on('connect', function() {socket.emit('start_processing', {'filename': filename});});socket.on('progress', function(msg) {var progress = msg.data;var progressBarFill = document.getElementById('progress-bar-fill');progressBarFill.style.width = progress + '%';progressBarFill.innerText = progress + '%';});socket.on('result', function(msg) {// 将结果保存到 LocalStoragelocalStorage.setItem('original', msg.original);localStorage.setItem('translated', msg.translated);// 跳转到结果页面window.location.href = '/result';});socket.on('error', function(msg) {alert('错误:' + msg.data);window.location.href = '/';});</script>
</body>
</html>

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: 90vh;}.original {background-color: #f9f9f9;}.translated {background-color: #eef9f1;}pre {white-space: pre-wrap;word-wrap: break-word;}</style>
</head>
<body><h1>翻译结果</h1><div class="container"><div class="content original"><h2>原文</h2><pre id="original-text"></pre></div><div class="content translated"><h2>译文</h2><pre id="translated-text"></pre></div></div><script>// 从 LocalStorage 获取结果document.getElementById('original-text').innerText = localStorage.getItem('original');document.getElementById('translated-text').innerText = localStorage.getItem('translated');// 清除 LocalStoragelocalStorage.removeItem('original');localStorage.removeItem('translated');</script>
</body>
</html>

Dockerfile

# 使用官方的 Python 3.9 slim 版作为基础镜像
FROM python:3.12.3-slim# 安装必要的系统依赖
RUN apt-get update && apt-get install -y \tesseract-ocr \poppler-utils \libtesseract-dev \libleptonica-dev \pkg-config \&& rm -rf /var/lib/apt/lists/*# 设置工作目录
WORKDIR /app# 复制当前目录内容到工作目录
COPY . /app# 安装 Python 依赖
RUN pip install --upgrade pipRUN pip install --no-cache-dir -r requirements.txt# 暴露应用运行的端口
EXPOSE 9004# 设置环境变量,防止 Python 输出被缓冲
ENV PYTHONUNBUFFERED=1# 运行应用
CMD ["python", "app.py"]

requirements.txt

Flask
flask_socketio
pdf2image
pytesseract
deep_translator
Werkzeug
eventlet

Windows 运行

  1. 安装依赖环境: 在 Windows 系统上,首先需要安装 Python 以及所需的库。主要依赖库见 requirements.txt:

  2. 运行项目: cmd: python app.py 脚本来启动项目,用户可以通过本地浏览器访问应用,上传需要翻译的 PDF 文件。

  3. OCR tesseract 下载: Home · UB-Mannheim/tesseract Wiki · GitHub

  4. 因为PDF2IMAGE 赖于 Poppler 来将 PDF 转换为图像, 需要下载windows 的二进制程序:
    https://github.com/oschwartz10612/poppler-windows 并且要加入到系统的路径中。如果你同我一样没有重启 Visual Studio Code,它不能加入PATH更新的内容。

怎么加path,见下图:

        5. 重启cmd在项目目录下,运行 python app.py

Docker 部署

如果用户希望更方便地在服务器或其他平台上部署该工具,可以选择使用 Docker。下面是 Docker 的部署步骤:

必要文件

Dockerfile, requirements.txt, 程序4文件, 如果是Linux不需要单独手动安装OCR与Poppler

构建 Docker 镜像:在Dockerfile目录下

docker build -t pdf2tx . #创建一个image

docker run -d -p 9004:9004 --name pdf2tx-containter pdf2tx       # 指定运行端口号是9004, 命名容器的名字 pdf2tx-container, 来自镜像 pdf2tx

运行容器

docker start pdf2tx-container

这样,用户可以通过访问 http://NAS_IP:9004 来使用翻译功能。

总结

pdf2tx 提供了一个便捷的方案,用于将图片 PDF 文件中的内容翻译为中文,无需手动提取和处理图片文本。用户只需上传 PDF 文件,工具会自动完成文字识别与翻译,并最终以对照的形式展示翻译结果。无论是在 Windows 系统中直接运行,还是通过 Docker 部署,pdf2tx 都能为用户提供高效的翻译体验。

另,试着在NAS上添加swapfile内存

如果在家用QNAP NAS上使用这个程序,如果PDF文件比较大,可能会造成内存不够,使NAS 宕机。

我的TS-453D 使用top命令,看到:
Mem: 3671064K used, 185496K free, 34592K shrd, 6844K buff, 409892K cached
试了一本400页的,NAS就崩溃了   (物理4GB内存+16GB swap)

我记得以前的UNIX/linux还有个swapfile做扩展内存,它已经进化的没有了吗?

在NAS终端里,运行命令:

[/share/CACHEDEV1_DATA] # df -h
Filesystem                Size      Used Available Use% Mounted on
none                    400.0M    347.4M     52.6M  87% /
devtmpfs                  1.8G      4.0K      1.8G   0% /dev
tmpfs                    64.0M      1.2M     62.8M   2% /tmp
tmpfs                     1.8G    208.0K      1.8G   0% /dev/shm
tmpfs                    16.0M      4.0K     16.0M   0% /share
/dev/mmcblk0p5            7.7M     52.0K      7.7M   1% /mnt/boot_config
tmpfs                    16.0M         0     16.0M   0% /mnt/snapshot/export
/dev/md9                493.5M    240.6M    252.9M  49% /mnt/HDA_ROOT
cgroup_root               1.8G         0      1.8G   0% /sys/fs/cgroup
/dev/mapper/vg1-lv1312352.1M    232.0K    351.9M   0% /mnt/pool1
/dev/mapper/cachedev13.5T    642.6G      2.9T  18% /share/CACHEDEV1_DATA
/dev/md13               417.0M    381.7M     35.3M  92% /mnt/ext
tmpfs                    32.0M     27.2M      4.8M  85% /samba_third_party
tmpfs                     1.0M         0      1.0M   0% /share/external/.nd
tmpfs                     1.0M         0      1.0M   0% /share/external/.cm
tmpfs                     1.0M         0      1.0M   0% /mnt/hm/temp_mount
cmfs                      1.7T         0      1.7T   0% /share/external/.cm/0/1b7385b24-4af8-4481-a288-5f1e61a03cee
cmfs                      1.7T         0      1.7T   0% /share/external/.cm/0/1d6888667-4502-4d4f-ac2e-c47b4e3e2344
tmpfs                    48.0M     40.0K     48.0M   0% /share/CACHEDEV1_DATA/.samba/lock/msg.lock
tmpfs                    16.0M         0     16.0M   0% /mnt/ext/opt/samba/private/msg.sock
tmpfs                    10.0M         0     10.0M   0% /tmp/wfm
/dev/mapper/vg1-snap100023.5T    638.7G      2.9T  18% /mnt/snapshot/1/10002
/dev/mapper/vg1-snap100033.5T    639.3G      2.9T  18% /mnt/snapshot/1/10003
tmpfs                    64.0M      1.2M     62.8M   2% /share/CACHEDEV1_DATA/.qpkg/MediaSignPlayer/CodexPackExt/tmp
hdsfusemnt              400.0M    347.4M     52.6M  87% /share/CACHEDEV1_DATA/.qpkg/MediaSignPlayer/CodexPackExt/share
none                      1.8G         0      1.8G   0% /share/CACHEDEV1_DATA/.qpkg/MediaSignPlayer/CodexPackExt/sys/fs/cgroup
udev                      1.8G      4.0K      1.8G   0% /share/CACHEDEV1_DATA/.qpkg/MediaSignPlayer/CodexPackExt/dev
none                      1.8G         0      1.8G   0% /share/CACHEDEV1_DATA/.qpkg/MediaSignPlayer/CodexPackExt/dev/shm
tmpfs                     1.8G         0      1.8G   0% /share/CACHEDEV1_DATA/.qpkg/MediaSignPlayer/CodexPackExt/run
none                      1.8G         0      1.8G   0% /share/CACHEDEV1_DATA/.qpkg/MediaSignPlayer/CodexPackExt/run/lock
none                      1.8G         0      1.8G   0% /share/CACHEDEV1_DATA/.qpkg/MediaSignPlayer/CodexPackExt/run/user
tmpfs                   100.0K         0    100.0K   0% /share/CACHEDEV1_DATA/Container/container-station-data/lib/lxd/shmounts

df -h 找到大的空间   我选中 /share/CACHEDEV1_DATA, 这里有2.9TB,如下图

用命令生成一个全0的4GB文件

[/share/CACHEDEV1_DATA] # dd if=/dev/zero of=/share/CACHEDEV1_DATA/swapfile bs=1M count=4096
4096+0 records in
4096+0 records out
4294967296 bytes (4.0GB) copied, 20.826660 seconds, 196.7MB/s
  • if=/dev/zero:指定输入文件为 /dev/zero,用于生成零值字节的特殊设备文件。
  • of=/share/CACHEDEV1_DATA/swapfile 指定输出文件路径。
  • bs=1M count=4096:创建一个 4GB 大小的文件,每块 1MB,共 4096 个块。
[/share/CACHEDEV1_DATA] # ls -l
total 4194400
-rw-------  1 admin administrators       9216 2022-01-18 05:00 aquota.user
drwxrwxrwx  4 admin administrators       4096 2022-12-14 07:48 Container/
drwxrwxrwx 13 admin administrators       4096 2024-09-29 17:55 Download/
drwxrwxrwx 10 admin administrators       4096 2024-08-03 17:17 homes/
drwx------  2 admin administrators      16384 2020-12-12 07:57 lost+found/
drwxrwxrwx 20 admin administrators       4096 2024-10-05 01:17 Multimedia/
drwxrwxrwx  5 admin administrators       4096 2024-10-05 21:53 Public/
drwxrwxrwx  4 admin administrators       4096 2023-06-13 19:58 QVRCenterAppData/
drwxrwxrwx  5 admin administrators       4096 2023-06-13 00:45 QVRProAppData/
drwxrwxrwx  3 admin administrators       4096 2023-06-12 01:57 QVRProAutoSnap/
drwxrwxrwx  7 admin administrators       4096 2023-07-15 19:45 QVRProDB/
drwxrwxrwx  4 admin administrators       4096 2023-06-12 01:59 QVRProRecording/
drwxrwxrwx  5 admin administrators       4096 2023-12-15 19:40 Shard/
drwxr-xr-x 15 admin administrators       4096 2024-10-05 21:57 _.share/
drwxrwxrwx  9 admin administrators       4096 2024-08-19 19:09 Storage-Bank/
-rw-r--r--  1 admin administrators 4294967296 2024-10-05 23:12 swapfile
drwxrwxrwx  6 admin administrators       4096 2024-10-05 23:15 Web/
[/share/CACHEDEV1_DATA] # 

可以在上面看到: -rw-r--r--  1 admin administrators 4294967296 2024-10-05 23:12 swapfile

它只需要系统来读取就行,所以用chmod 600 修改权限,见下面:

[/share/CACHEDEV1_DATA] # chmod 600 /share/CACHEDEV1_DATA/swapfile
[/share/CACHEDEV1_DATA] # ls -l
total 4194400
-rw-------  1 admin administrators       9216 2022-01-18 05:00 aquota.user
drwxrwxrwx  4 admin administrators       4096 2022-12-14 07:48 Container/
drwxrwxrwx 13 admin administrators       4096 2024-09-29 17:55 Download/
drwxrwxrwx 10 admin administrators       4096 2024-08-03 17:17 homes/
drwx------  2 admin administrators      16384 2020-12-12 07:57 lost+found/
drwxrwxrwx 20 admin administrators       4096 2024-10-05 01:17 Multimedia/
drwxrwxrwx  5 admin administrators       4096 2024-10-05 21:53 Public/
drwxrwxrwx  4 admin administrators       4096 2023-06-13 19:58 QVRCenterAppData/
drwxrwxrwx  5 admin administrators       4096 2023-06-13 00:45 QVRProAppData/
drwxrwxrwx  3 admin administrators       4096 2023-06-12 01:57 QVRProAutoSnap/
drwxrwxrwx  7 admin administrators       4096 2023-07-15 19:45 QVRProDB/
drwxrwxrwx  4 admin administrators       4096 2023-06-12 01:59 QVRProRecording/
drwxrwxrwx  5 admin administrators       4096 2023-12-15 19:40 Shard/
drwxr-xr-x 15 admin administrators       4096 2024-10-05 21:57 _.share/
drwxrwxrwx  9 admin administrators       4096 2024-08-19 19:09 Storage-Bank/
-rw-------  1 admin administrators 4294967296 2024-10-05 23:12 swapfile
drwxrwxrwx  6 admin administrators       4096 2024-10-05 23:15 Web/
[/share/CACHEDEV1_DATA] # 

把它格式化成交换空间,老unix命令,我用的第一台UNIX是 Siemens Nixdorf 塔式机+oracle 7. 暴露年纪了。后来管过 SunOS, 这些证都是塑料垃圾了。

[/share/CACHEDEV1_DATA] # mkswap /share/CACHEDEV1_DATA/swapfile
Setting up swapspace version 1, size = 4294963 kB
[/share/CACHEDEV1_DATA] # 

启用交换文件

[/share/CACHEDEV1_DATA] # swapon /share/CACHEDEV1_DATA/swapfile
[/share/CACHEDEV1_DATA] # 

验证交换是否启用成功

[/share/CACHEDEV1_DATA] # swapon --show
swapon: unrecognized option '--show'
BusyBox v1.24.1 (2024-08-17 02:09:18 CST) multi-call binary.Usage: swapon [-a] [-e] [-d[POL]] [-p PRI] [DEVICE]Start swapping on DEVICE-a      Start swapping on all swap devices-d[POL] Discard blocks at swapon (POL=once),as freed (POL=pages), or both (POL omitted)-e      Silently skip devices that do not exist-p PRI  Set swap device priority
[/share/CACHEDEV1_DATA] # free -h
BusyBox v1.24.1 (2024-08-17 02:09:18 CST) multi-call binary.Usage: free [-b/k/m/g]Display the amount of free and used system memory
[/share/CACHEDEV1_DATA] # free -gtotal       used       free     shared    buffers     cached
Mem:             3          3          0          0          0          0
-/+ buffers/cache:          2          1
Swap:           34         16         17
[/share/CACHEDEV1_DATA] # cat /proc/swaps
Filename                                Type            Size            Used            Priority
/dev/md321                              partition       7751228         7750340         -2
/dev/md256                              partition       530108          512896          -3
/dev/md322                              partition       6702652         5444144         -4
/share/CACHEDEV1_DATA/.swap/qnap_swap   file            16777212        3430804         -5
/share/CACHEDEV1_DATA/swapfile          file            4194300         0               -6
[/share/CACHEDEV1_DATA] # 

开始,用swapon --show,提示没有这参数,(无法辨识 --show)。 又用了 free -h, 没看明白???怎么是34GB。  问CHATGPT:swapon 版本是 BusyBox 提供的精简版,因此不支持 --show 选项。它建议看 /proc/swaps

如果NAS重启,命令添加的swapfile就会在下次消失。 你可以考虑把它加到起动里。

比如 /etc/init.d/

放在 /etc/config/crontab 里

后续问题

性能
翻译10页laptop明显快于NAS数倍。
只把pdf转为txt,然后用 chrome那些翻译插件,可能效率跟高。
分解PDF为多个,并多线程运行,再把结果拼接,效率会提高。

相关文章:

<Project-6 pdf2tx> Python Flask 应用:图片PDF图书的中文翻译解决方案

重要更新&#xff01; Modified on 8oct24. P6已经被 P8 替代&#xff0c;后着支持多任务&#xff0c;多翻译机。在速度与资源占用上&#xff0c;都好于这个P6。 新的 P8 文章链接&#xff1a; &#xff1c;Project-8 pdf2tx-MM&#xff1e; Python Flask应用&#xff1a;在…...

10.11Python数学基础-多维随机变量及其分布

多维随机变量及其分布 1.二维随机变量及其分布 假设E是随机试验&#xff0c;Ω是样本空间&#xff0c;X、Y是Ω的两个变量&#xff1b;(X,Y)就叫做二维随机变量或二维随机向量。X、Y来自同一个样本空间。 联合分布函数 F ( x , y ) P ( X ≤ x , Y ≤ y ) F(x,y)P(X≤x,Y≤…...

(四)Mysql 数据库备份恢复全攻略

一、数据库备份 数据库备份目的和数据库故障类型 目的&#xff1a; 当发生故障时&#xff0c;将损失降到最低。保证能够快速从备份数据中恢复&#xff0c;确保数据稳定运行。故障类型&#xff1a; 程序错误&#xff1a;Mysql 服务器端程序故障无法使用。人为误操作&#xff1a;…...

在MySQL 8.0中,如何更好地管理索引以节省空间和提高查询效率?

1. 索引选择与设计 选择合适的列&#xff1a;确保索引覆盖的列是经常用于查询条件、排序或连接操作的列。避免冗余索引&#xff1a;检查并移除重复或不必要的索引。例如&#xff0c;如果已经有一个 INDEX(a, b)&#xff0c;那么单独的 INDEX(a) 可能是多余的。使用复合索引&am…...

图形化编程(013)——“面向鼠标指针”积木块

知识回顾 1、舞台和坐标的知识 2、使用坐标控制角色移动 一句俗语&#xff1a;大鱼吃小鱼&#xff0c;小鱼吃虾米&#xff0c;感觉挺有意思的。 这句话说明了自然界中的生存法则&#xff0c;本次分享我与大家共同做一个大鱼吃小鱼的作品。 案例解说&#xff1a; 点击绿旗…...

【Spring】Spring Boot项目创建和目录介绍

文章目录 1 Spring Boot 介绍2 Spring Boot 项目创建注意事项 3. 项目代码和目录介绍pom 文件父工程目录介绍 1 Spring Boot 介绍 Spring 让 Java 程序更加快速、简单和安全&#xff0c;Spring 对于速度、简单性和生产力的关注使其成为世界上最流行的 Java 框架 Spring 官方提…...

第十二章 RabbitMQ之失败消息处理策略

目录 一、引言 二、RepublishMessageRecoverer 实现 2.1. 实现步骤 2.2. 实现代码 2.2.1. 异常交换机队列回收期配置类 2.2.2. 常规交换机队列配置类 2.2.3. 消费者代码 2.2.4. 消费者yml配置 2.2.5. 生产者代码 2.2.6. 生产者yml配置 2.2.7. 运行效果 一、引言 …...

23年408数据结构

第一题&#xff1a; 解析&#xff1a; 第一点&#xff0c;我们要知道顺序存储的特点&#xff1a;优点就是随用随取&#xff0c;就是你想要查询第几个元素可以直接查询出来&#xff0c;时间复杂度就是O(1)&#xff0c;缺点就是不适合删除和插入&#xff0c;因为每次删除和插入一…...

vue3ElementPlu表格合并多行

// 单元格合并逻辑 const objectSpanMethod ({ row, rowIndex, columnIndex }) > { const previousMachineModelUniqueId rowIndex > 0 ? tableData.value[rowIndex - 1].machineModel : null; const currentMachineModelUniqueId row.machineModel; // 合并“机型”…...

MySQL数据库 - 索引(上)

目录 1 简介 1.1 索引是什么 1.2 为什么要使用索引 2 索引应该选择哪种数据结构 2.1 HASH 2.2 二叉搜索树 2.3 N叉树&#xff08;B树&#xff09; 2.4 B树 3 MySQL的页 3.1 为什么要使用页 3.2 页文件头和页文件尾 3.3 页主体 3.4 页目录 4 B树在MySQL索引中的应…...

redis与springBoot整合

前提 要实现,使用Redis存储登录状态 需要一个完整的前端后端的项目 前端项目搭建 解压脚手架 安装依赖 配置请求代理 选做: 禁用EsLint语法检查 Vue Admin Template关闭eslint校验&#xff0c;lintOnSave&#xff1a;false设置无效解决办法_lintonsave: false-CSDN博客 …...

YoloV9改进策略:BackBone改进|CAFormer在YoloV9中的创新应用,显著提升目标检测性能

摘要 在目标检测领域,模型性能的提升一直是研究者和开发者们关注的重点。近期,我们尝试将CAFormer模块引入YoloV9模型中,以替换其原有的主干网络,这一创新性的改进带来了显著的性能提升。 CAFormer,作为MetaFormer框架下的一个变体,结合了深度可分离卷积和普通自注意力…...

消防应急物资仓库管理系统

集驰电子消防装备仓库管理系统(DW-S302系统)是一套成熟系统&#xff0c;依托3D技术、大数据、RFID技术、数据库技术、对装备器材进行统一管理&#xff0c;以RFID射频识别技术为核心&#xff0c;构建以物资综合管理为基础&#xff0c;智能分析定位为主要特色功能的装备器材库综合…...

【论文阅读】Semi-Supervised Few-shot Learning via Multi-Factor Clustering

通过多因素聚类的半监督小样本学习 引用&#xff1a;Ling J, Liao L, Yang M, et al. Semi-supervised few-shot learning via multi-factor clustering[C]//Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition. 2022: 14564-14573. 论文地址…...

第十三章 RabbitMQ之消息幂等性

目录 一、引言 二、消息幂等解决方案 2.1. 方案一 2.2. 方案二 一、引言 幂等是一个数学概念&#xff0c;用函数表达式来描述是这样的&#xff1a;f(x) f(f(x)) 。在程序开发中&#xff0c;则是指同一个业务&#xff0c;执行一次或多次对业务状态的影响是一致的。有些业务…...

tpcms-master.zip

网盘&#xff1a;https://pan.notestore.cn/s.html?id34https://pan.notestore.cn/s.html?id34...

Spring国际化和Validation

SpringBoot国际化和Validation融合 场景 在应用交互时&#xff0c;可能需要根据客户端得语言来返回不同的语言数据。前端通过参数、请求头等往后端传入locale相关得参数&#xff0c;后端获取参数&#xff0c;根据不同得locale来获取不同得语言得文本信息返回给前端。 实现原…...

②EtherCAT转ModbusTCP, EtherCAT/Ethernet/IP/Profinet/ModbusTCP协议互转工业串口网关

EtherCAT/Ethernet/IP/Profinet/ModbusTCP协议互转工业串口网关https://item.taobao.com/item.htm?ftt&id822721028899 协议转换通信网关 EtherCAT 转 Modbus TCP (接上一章&#xff09; GW系列型号 配置说明 上载 网线连接电脑到模块上的 WEB 网页设置网口&#…...

【华为HCIP实战课程八】OSPF网络类型及报文类型详解,网络工程师

一、点到点网络类型 1、两台路由器 2、支持广播、组播 P2P(PPP、HDLC、帧中继子接口) 我们需要三个维度考虑 A、是否自动通过组播发现邻居 B、时间(Hello和Dead) C、DR和BDR----多点接入网络需要用到(广播和NBMA) 点到点是组播自动发现邻居,Hello 10S,Dead 40S…...

信息安全工程师(28)机房安全分析与防护

前言 机房安全分析与防护是一个复杂而细致的过程&#xff0c;涉及到物理安全、环境控制、电力供应、数据安全、设备管理、人员管理以及紧急预案等多个方面。 一、机房安全分析 1. 物理安全威胁 非法入侵&#xff1a;未经授权的人员可能通过门窗、通风口等进入机房&#xff0c;…...

大数据处理从零开始————9.MapReduce编程实践之信息过滤之学生成绩统计demo

1.项目目标 1.1 需求概述 现在我们要统计某学校学生的成绩信息&#xff0c;筛选出成绩在60分及以上的学生。 1.2 业务分析 如果我们想实现该需求&#xff0c;可以通过编写一个MapReduce程序&#xff0c;来处理包含学生信息的文本文件&#xff0c;每行包含【学生的姓名&#x…...

自动化测试 | 窗口截图

driver.get_screenshot_as_file 是 Selenium WebDriver 的一个方法&#xff0c;它允许你将当前浏览器窗口&#xff08;或标签页&#xff09;的截图保存为文件。这个方法对于自动化测试中的截图验证非常有用&#xff0c;因为它可以帮助你捕获测试执行过程中的页面状态。 以下是…...

初中数学网上考试系统的设计与实现(论文+源码)_kaic

初中数学网上考试系统的设计与实现 学生&#xff1a; 指导教师&#xff1a; 摘 要&#xff1a;科技在人类的历史长流中愈洗愈精&#xff0c;不仅包括人们日常的生活起居&#xff0c;甚至还包括了考试的变化。之前的考试需要大量的时间和精力&#xff0c;组织者还需要挑选并考查…...

关系运算(3)

关系代数 昨天讲完附加关系代数运算&#xff0c;今天讲扩展关系代数运算。 扩展代数运算 正如其名&#xff0c;这种运算定义了前面基本和附加都没有的运算。 去重运算 可以将关系R中跟查询条件相关但是形成了重复的元组去除&#xff0c;只保留查询结果&#xff08;简洁&…...

tp6的系统是如何上架的

TP6&#xff08;ThinkPHP6&#xff09;的系统上架过程&#xff0c;通常指的是将基于ThinkPHP6框架开发的应用程序部署到生产环境&#xff0c;并使其可以通过互联网访问。以下是一个大致的上架流程&#xff0c;包括准备工作、部署步骤以及后续维护等方面&#xff1a; 一、准备工…...

Vue:开发小技巧

目录 1. Table表格偏移 1. Table表格偏移 通过设置自小的宽度进行控制 :min-width <el-table-column label"操作" align"center" class-name"small-padding fixed-width" fixed"right" min-width"150px"><templa…...

力扣之1369.获取最近第二次的活动

题目&#xff1a; sql建表语句 Create table If Not Exists UserActivity (username varchar(30), activity varchar(30), startDate date, endDate date); Truncate table UserActivity; insert into UserActivity (username, activity, startDate, endDate) values (Alic…...

Python 和 Jupyter Kernel 版本不一致

使用jupyter notebook时明明已经安装了包&#xff0c;但是导入时提示&#xff1a; ModuleNotFoundError: No module named ptitprince 1、检查安装环境 !pip show ptitprince Name: ptitprince Version: 0.2.7 Summary: A Python implementation of Rainclouds, originally…...

Android常用布局

目录 布局文件中常见的属性 1. 基本布局属性 1&#xff09;android:layout_width 2&#xff09;android:layout_height 3&#xff09;android:layout_margin 4&#xff09;android:padding 2. 线性布局 (LinearLayout) 属性 1&#xff09;android:orientation 2&#xff09;and…...

初级网络工程师之从入门到入狱(五)

本文是我在学习过程中记录学习的点点滴滴&#xff0c;目的是为了学完之后巩固一下顺便也和大家分享一下&#xff0c;日后忘记了也可以方便快速的复习。 网络工程师从入门到入狱 前言一、链路聚合1.1、手动进行链路聚合1.1.1、 拓扑图&#xff1a;1.1.2、 LSW11.1.3、 LSW2 1.2、…...