重要更新!
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)。
-
上传 PDF 文件: 用户需要通过网页界面上传需要翻译的 PDF 文件。可以访问
upload.html
页面,选择 PDF 文件并点击上传按钮。 -
翻译过程: 文件上传后,系统会开始处理,显示处理进度。用户可以通过网页界面查看 OCR 与翻译的实时进展。
-
查看翻译结果: 翻译完成后,用户可以查看原文与译文的对比结果。网页会将翻译后的内容展示在左右两个部分,方便用户对照查看。
只下存了5页用于测试程序。
程序的逻辑
PDF转为图像
-> OCR提取图片中的文字
-> 文字交给翻译器
-> 翻译的结果在网页上显示
因为使用时,可能会用到未授权问题,上传代码没有保存内容的功能。
代码内容介绍
pdf2tx
的代码结构由以下几个关键部分组成:
-
主应用程序 (
app.py
):app.py
是整个项目的核心,用于处理用户请求、管理前端页面以及实现 OCR 和翻译功能。该程序基于 Flask 框架,提供了简单的 Web 服务,同时使用flask_socketio
实现前端与后端之间的实时通信 -
OCR 与翻译功能: OCR 和翻译功能是
pdf2tx
的核心逻辑。它们通过pdf2image
将 PDF 页面转换为图像,然后使用pytesseract
提取文本,再通过deep_translator
将提取的文本翻译为中文。 -
前端 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 # 结果展示页面
目录结构说明
app.py
:包含应用程序的核心逻辑,例如路由定义、文件处理、文字提取、翻译等。Dockerfile
:用于定义项目的 Docker 镜像,方便部署和环境管理。requirements.txt
:列出了项目所需的 Python 库,如 Flask、pdf2image
、pytesseract
等(requirements)。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 e
return 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):
# 使用唯一且安全的文件名使用UUID
ext = 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.sid
socketio.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)
return
filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
if not os.path.exists(filepath):
socketio.emit('error', {'data': '文件未找到'}, room=sid)
return
socketio.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)
return
socketio.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)
return
extracted_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)
return
socketio.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) {
// 将结果保存到 LocalStorage
localStorage.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');
// 清除 LocalStorage
localStorage.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 pip
RUN 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 运行
-
安装依赖环境: 在 Windows 系统上,首先需要安装 Python 以及所需的库。主要依赖库见 requirements.txt:
-
运行项目: cmd: python app.py 脚本来启动项目,用户可以通过本地浏览器访问应用,上传需要翻译的 PDF 文件。
-
OCR tesseract 下载: Home · UB-Mannheim/tesseract Wiki · GitHub
-
因为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-lv1312
352.1M 232.0K 351.9M 0% /mnt/pool1
/dev/mapper/cachedev1
3.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-snap10002
3.5T 638.7G 2.9T 18% /mnt/snapshot/1/10002
/dev/mapper/vg1-snap10003
3.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 -g
total 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为多个,并多线程运行,再把结果拼接,效率会提高。