mirror of
https://github.com/bestnite/slide-translate.git
synced 2025-10-27 17:01:18 +00:00
init
This commit is contained in:
13
.gitignore
vendored
Normal file
13
.gitignore
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Python-generated files
|
||||||
|
__pycache__/
|
||||||
|
*.py[oc]
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
wheels/
|
||||||
|
*.egg-info
|
||||||
|
|
||||||
|
# Virtual environments
|
||||||
|
.venv
|
||||||
|
input/
|
||||||
|
output/
|
||||||
|
config.ini
|
||||||
1
.python-version
Normal file
1
.python-version
Normal file
@@ -0,0 +1 @@
|
|||||||
|
3.13
|
||||||
50
README.md
Normal file
50
README.md
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
## 留子课程幻灯片整理翻译工具
|
||||||
|
|
||||||
|
本项目旨在为海外留学生提供一个高效、智能的课程资料处理解决方案,以应对他们在学习过程中遇到的语言障碍和复杂的幻灯片整理挑战。
|
||||||
|
|
||||||
|
**核心问题:** 许多留学生在面对英文或其他语言的课程幻灯片时,不仅需要理解专业内容,还要克服语言隔阂,并且手动整理和翻译耗时费力,容易遗漏关键信息,尤其是在处理含有大量图表的幻灯片时。
|
||||||
|
|
||||||
|
**程序流程:**
|
||||||
|
|
||||||
|
1. **自动化内容提取与转换:** 将 PDF 格式的课程幻灯片**自动转换为结构化的 Markdown 格式**,便于后续编辑和阅读。
|
||||||
|
2. **智能格式优化与增强:** 利用**大型语言模型 (LLM) 进行深度处理,对转换后的 Markdown 内容进行微调,优化版面格式,并智能地为图片增加中文注解**,提升理解效率。
|
||||||
|
3. **精准专业翻译:** 将内容**翻译成简体中文,同时智能识别并保留专业名词的英文原文注解**,确保专业术语的准确性,避免翻译歧义,让学生在中文语境下理解内容的同时,也能熟悉和掌握专业英文表达。
|
||||||
|
|
||||||
|
### 安装
|
||||||
|
|
||||||
|
1. **安装 uv:** 如果您尚未安装 `uv`,请按照官方文档进行安装。通常可以使用 pip 安装:
|
||||||
|
```bash
|
||||||
|
pip install uv
|
||||||
|
```
|
||||||
|
2. **安装依赖:** 在项目根目录下,使用 `uv` 安装所有必要的依赖:
|
||||||
|
```bash
|
||||||
|
uv pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
或者,如果您的项目使用 `pyproject.toml`:
|
||||||
|
```bash
|
||||||
|
uv pip install .
|
||||||
|
```
|
||||||
|
|
||||||
|
### 配置
|
||||||
|
|
||||||
|
本项目使用 `config.ini` 文件来管理 API 密钥。请确保在运行程序之前,在项目根目录下创建 `config.ini` 文件,并按照以下格式配置您的 `GOOGLE_API_KEY`:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[api_keys]
|
||||||
|
GOOGLE_API_KEY = 您的Google API密钥
|
||||||
|
```
|
||||||
|
|
||||||
|
请将 `您的Google API密钥` 替换为您的实际 Google API 密钥。
|
||||||
|
|
||||||
|
### 使用方法
|
||||||
|
|
||||||
|
1. 将需要处理的 PDF 文件放入 `input` 目录下。
|
||||||
|
2. 运行 `main.py` 脚本,并提供 PDF 文件名作为命令行参数。请使用 `uv run` 命令来执行脚本,以确保在正确的虚拟环境中运行:
|
||||||
|
```bash
|
||||||
|
uv run python main.py <pdf_file_name>
|
||||||
|
```
|
||||||
|
例如:
|
||||||
|
```bash
|
||||||
|
uv run python main.py slides.pdf
|
||||||
|
```
|
||||||
|
请确保 PDF 文件位于 `input` 目录中。
|
||||||
208
main.py
Normal file
208
main.py
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
import base64
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import configparser
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from docling.datamodel.accelerator_options import AcceleratorDevice, AcceleratorOptions
|
||||||
|
from docling.datamodel.base_models import InputFormat
|
||||||
|
from docling.datamodel.pipeline_options import (
|
||||||
|
PdfPipelineOptions,
|
||||||
|
)
|
||||||
|
from docling.datamodel.settings import settings
|
||||||
|
from docling.document_converter import DocumentConverter, PdfFormatOption
|
||||||
|
from docling_core.types.doc.base import ImageRefMode
|
||||||
|
from langchain_core.messages import HumanMessage, SystemMessage
|
||||||
|
from langchain_google_genai import ChatGoogleGenerativeAI
|
||||||
|
|
||||||
|
|
||||||
|
def convert_pdf_to_markdown(input_doc_path, output_md_path):
|
||||||
|
"""Converts a PDF document to Markdown format."""
|
||||||
|
accelerator_options = AcceleratorOptions(
|
||||||
|
num_threads=8, device=AcceleratorDevice.CUDA
|
||||||
|
)
|
||||||
|
|
||||||
|
pipeline_options = PdfPipelineOptions()
|
||||||
|
pipeline_options.accelerator_options = accelerator_options
|
||||||
|
pipeline_options.do_ocr = True
|
||||||
|
pipeline_options.do_table_structure = True
|
||||||
|
pipeline_options.table_structure_options.do_cell_matching = True
|
||||||
|
pipeline_options.generate_page_images = True
|
||||||
|
pipeline_options.generate_picture_images = True
|
||||||
|
|
||||||
|
converter = DocumentConverter(
|
||||||
|
format_options={
|
||||||
|
InputFormat.PDF: PdfFormatOption(
|
||||||
|
pipeline_options=pipeline_options,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Enable the profiling to measure the time spent
|
||||||
|
settings.debug.profile_pipeline_timings = True
|
||||||
|
|
||||||
|
# Convert the document
|
||||||
|
print(f"Converting {input_doc_path} to Markdown...")
|
||||||
|
conversion_result = converter.convert(input_doc_path)
|
||||||
|
doc = conversion_result.document
|
||||||
|
|
||||||
|
# List with total time per document
|
||||||
|
doc_conversion_secs = conversion_result.timings["pipeline_total"].times
|
||||||
|
|
||||||
|
doc.save_as_markdown(
|
||||||
|
filename=Path(output_md_path),
|
||||||
|
artifacts_dir=Path(os.path.join(os.path.splitext(os.path.basename(output_md_path))[0], "image")),
|
||||||
|
image_mode=ImageRefMode.REFERENCED,
|
||||||
|
)
|
||||||
|
print(f"Conversion took: {doc_conversion_secs} seconds")
|
||||||
|
print(f"Markdown file saved to: {output_md_path}")
|
||||||
|
|
||||||
|
|
||||||
|
def simplify_image_references_in_markdown(markdown_path):
|
||||||
|
"""Simplifies image names in the markdown file and renames the image files."""
|
||||||
|
print(f"Simplifying image references in {markdown_path}...")
|
||||||
|
with open(markdown_path, "r+", encoding="utf-8") as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# Find all unique image paths
|
||||||
|
image_paths = set(re.findall(r"\((\S*?image_\d{6}_[a-f0-9]+\.png)\)", content))
|
||||||
|
|
||||||
|
for old_path in image_paths:
|
||||||
|
old_path_prefix = os.path.join("output", old_path)
|
||||||
|
if not os.path.exists(path=old_path_prefix):
|
||||||
|
continue
|
||||||
|
|
||||||
|
directory = os.path.dirname(old_path_prefix)
|
||||||
|
old_filename = os.path.basename(old_path_prefix)
|
||||||
|
|
||||||
|
# Create new filename, e.g., image_000000.png
|
||||||
|
parts = old_filename.split("_")
|
||||||
|
new_filename = f"{parts[0]}_{parts[1]}.png"
|
||||||
|
new_path = os.path.join(directory, new_filename)
|
||||||
|
|
||||||
|
# Rename the physical file
|
||||||
|
if not os.path.exists(new_path):
|
||||||
|
os.rename(old_path_prefix, new_path)
|
||||||
|
|
||||||
|
# Replace the path in the markdown content
|
||||||
|
new_path_in_markdown = new_path.replace(f"output{os.sep}", "")
|
||||||
|
content = content.replace(old_path, new_path_in_markdown)
|
||||||
|
|
||||||
|
# Go back to the beginning of the file and write the modified content
|
||||||
|
f.seek(0)
|
||||||
|
f.write(content)
|
||||||
|
f.truncate()
|
||||||
|
print("Image references simplified.")
|
||||||
|
|
||||||
|
|
||||||
|
def refine_and_translate_content(markdown_path, pdf_path):
|
||||||
|
"""Refines and translates the Markdown content using an LLM."""
|
||||||
|
print("Starting content refinement and translation...")
|
||||||
|
|
||||||
|
config = configparser.ConfigParser()
|
||||||
|
config.read('config.ini')
|
||||||
|
google_api_key = config.get('api_keys', 'GOOGLE_API_KEY', fallback=None)
|
||||||
|
|
||||||
|
if not google_api_key:
|
||||||
|
print("Error: GOOGLE_API_KEY not found in config.ini")
|
||||||
|
return
|
||||||
|
|
||||||
|
os.environ["GOOGLE_API_KEY"] = google_api_key
|
||||||
|
try:
|
||||||
|
llm = ChatGoogleGenerativeAI(model="gemini-2.5-flash", temperature=0)
|
||||||
|
except Exception as e:
|
||||||
|
print(
|
||||||
|
f"Error initializing LLM. Make sure your Google API key is set correctly. Error: {e}"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(markdown_path, "rb") as f:
|
||||||
|
markdown_content = f.read()
|
||||||
|
|
||||||
|
with open(pdf_path, "rb") as pdf_file:
|
||||||
|
pdf_bytes = pdf_file.read()
|
||||||
|
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
print(f"Error reading files: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
prompt = """
|
||||||
|
您是一名专业的科技文档编辑和翻译。您的任务是润色一份从随附 PDF 文档自动转换而来的 Markdown 文本。请以原始 PDF 作为布局、图像和上下文的真实依据。
|
||||||
|
|
||||||
|
请根据提供的 Markdown 和 PDF 执行以下四项操作:
|
||||||
|
|
||||||
|
1. **清理多余字符**:查看 Markdown 文本,删除原始 PDF 中不存在的任何转换伪影或奇怪格式。
|
||||||
|
2. **解释图像内容**:参考 PDF 中的图表、示意图和图像,在图像引用后添加清晰简洁的解释。
|
||||||
|
3. **更正列表格式**:转换可能使嵌套列表扁平化。分析 PDF 中的列表结构,并在 Markdown 中恢复正确的多级缩进。
|
||||||
|
4. **翻译成中文**:将整个清理和更正后的文档翻译成简体中文。当您遇到专业或技术术语时,您必须在其译文旁边保留原始英文术语并用括号括起来。
|
||||||
|
|
||||||
|
只需要输出调整翻译后的 markdown 文本,不需要任何其他的文字内容。
|
||||||
|
"""
|
||||||
|
|
||||||
|
message_content = [
|
||||||
|
SystemMessage(prompt),
|
||||||
|
HumanMessage(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"type": "media",
|
||||||
|
"mime_type": "text/markdown",
|
||||||
|
"data": base64.b64encode(markdown_content).decode("utf-8"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "这是原始的PDF文件:\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "media",
|
||||||
|
"mime_type": "application/pdf",
|
||||||
|
"data": base64.b64encode(pdf_bytes).decode("utf-8"),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
print(
|
||||||
|
"Sending request to Gemini with the PDF and Markdown... This may take a moment."
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
response = llm.invoke(message_content)
|
||||||
|
refined_content = response.content
|
||||||
|
except Exception as e:
|
||||||
|
print(f"An error occurred while invoking the LLM: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
refined_output_path = os.path.splitext(markdown_path)[0] + "_refined_zh.md"
|
||||||
|
with open(refined_output_path, "w", encoding="utf-8") as f:
|
||||||
|
f.write(str(refined_content))
|
||||||
|
|
||||||
|
print(f"Task complete! Refined and translated file saved to: {refined_output_path}")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print("Usage: python main.py <pdf_file_name>")
|
||||||
|
print("Example: python main.py material.pdf")
|
||||||
|
print("Make sure you put pdf file into input directory")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
fileName = sys.argv[1]
|
||||||
|
if not fileName.endswith(".pdf"):
|
||||||
|
print("Error: The provided file must be a PDF file (e.g., 08.pdf)")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
input_doc_path = os.path.join("input", fileName)
|
||||||
|
output_md_path = os.path.join("output", fileName.replace(".pdf", ".md"))
|
||||||
|
|
||||||
|
# Step 1: Convert PDF to Markdown
|
||||||
|
convert_pdf_to_markdown(input_doc_path, output_md_path)
|
||||||
|
|
||||||
|
# Step 2: Simplify image references
|
||||||
|
simplify_image_references_in_markdown(output_md_path)
|
||||||
|
|
||||||
|
# # Step 3: Refine and translate the content
|
||||||
|
refine_and_translate_content(output_md_path, input_doc_path)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
12
pyproject.toml
Normal file
12
pyproject.toml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
[project]
|
||||||
|
name = "slide-translate"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Add your description here"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.13"
|
||||||
|
dependencies = [
|
||||||
|
"docling>=2.57.0",
|
||||||
|
"langchain>=1.0.2",
|
||||||
|
"langchain-google-genai>=3.0.0",
|
||||||
|
"langchain-ollama>=1.0.0",
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user