File size: 9,163 Bytes
179eccc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
> [!NOTE]
> 本文档可能包含 AI 生成内容。虽然我们力求准确,但仍可能存在不准确之处。请通过以下方式报告任何问题:
>
> - [GitHub Issues](https://github.com/PDFMathTranslate-next/PDFMathTranslate-next/issues)
> - 社区贡献(欢迎提交 PR!)

## Python API: do_translate_async_stream

### 概述
- do_translate_async_stream 是底层的异步入口点,用于翻译单个 PDF 并生成事件流(进度/错误/完成)。
- 它适用于构建您自己的 UI 或 CLI,在这些场景中您需要实时进度和对结果的完全控制。
- 它接受一个经过验证的 SettingsModel 和一个文件路径,并返回一个字典事件的异步生成器。

### 签名
- 导入:`from pdf2zh_next.high_level import do_translate_async_stream`
- 调用:`async for event in do_translate_async_stream(settings, file): ...`
- 参数:
  - settings: SettingsModel。必须有效;该函数将调用 `settings.validate_settings()`。
  - file: str | pathlib.Path。要翻译的单个 PDF 文件。必须存在。

注意:

- `settings.basic.input_files` 被此函数忽略;仅翻译给定的 `file`。
- 如果 `settings.basic.debug` 为 True,翻译在主进程中运行;否则在子进程中运行。两种情况下的事件模式是相同的。

### 事件流契约
异步生成器会产生以下类型的类 JSON 字典事件:

- 阶段摘要事件:`stage_summary`(可选,可能首先出现)
  - 字段
    - `type`:"stage_summary"
    - `stages`:对象列表 `{ "name": str, "percent": float }`,描述估计的工作分布
    - `part_index`:对于此摘要事件可能为 0
    - `total_parts`:总部分数(>= 1)

- 进度事件:`progress_start`、`progress_update`、`progress_end`
  - 公共字段
    - `type`:上述之一
    - `stage`:人类可读的阶段名称(例如,"解析 PDF 并创建中间表示"、"翻译段落"、"保存 PDF")
    - `stage_progress`:[0, 100] 范围内的浮点数,指示当前阶段内的进度
    - `overall_progress`:[0, 100] 范围内的浮点数,指示总体进度
    - `part_index`:当前部分索引(对于进度事件通常从 1 开始)
    - `total_parts`:总部分数(>= 1)。大型文档可能会自动拆分。
    - `stage_current`:阶段内的当前步骤
    - `stage_total`:阶段内的总步骤数

- 完成事件:`finish`
  - 字段
    - `type`:"finish"
    - `translate_result`:一个提供最终输出的**对象**(注意:不是字典,而是类实例)
      - `original_pdf_path`:输入 PDF 的路径
      - `mono_pdf_path`:单语翻译 PDF 的路径(或 None)
      - `dual_pdf_path`:双语翻译 PDF 的路径(或 None)
      - `no_watermark_mono_pdf_path`:无水印单语输出的路径(如果已生成),否则为 None
      - `no_watermark_dual_pdf_path`:无水印双语输出的路径(如果已生成),否则为 None
      - `auto_extracted_glossary_path`:自动提取的术语表 CSV 的路径(或 None)
      - `total_seconds`:经过的秒数(浮点数)
      - `peak_memory_usage`:翻译过程中的近似峰值内存使用量(浮点数;具体单位取决于实现)

- 错误事件:`error`
  - 字段
    - `type`:"error"
    - `error`:人类可读的错误消息
    - `error_type`:`BabeldocError`、`SubprocessError`、`IPCError`、`SubprocessCrashError` 等之一
    - `details`:可选的详细信息(例如,原始错误或回溯)

重要行为:
- 在进度开始之前,可能会发出一个可选的 `stage_summary`。
- 在某些故障情况下,生成器将首先产生一个 `error` 事件,然后抛出一个派生自 `TranslationError` 的异常。您应该既检查错误事件,又准备好捕获异常。
- `progress_update` 事件可能会重复出现相同的值;如有必要,消费者应进行去抖处理。
- 当您收到 `finish` 事件时,请停止消费流。

### 最小使用示例(异步)
```python
import asyncio
from pathlib import Path
from pdf2zh_next.high_level import do_translate_async_stream

# Assume you already have a valid SettingsModel instance named `settings`
# and a PDF file path

async def translate_one(settings, pdf_path: str | Path):
    try:
        async for event in do_translate_async_stream(settings, pdf_path):
            etype = event.get("type")

            if etype == "stage_summary":
                # Optional pre-flight summary of stages
                stages = event.get("stages", [])
                print("Stage summary:", ", ".join(f"{s['name']}:{s['percent']:.2f}" for s in stages))

            elif etype in {"progress_start", "progress_update", "progress_end"}:
                stage = event.get("stage")
                stage_prog = event.get("stage_progress")  # 0..100
                overall = event.get("overall_progress")  # 0..100
                part_i = event.get("part_index")
                part_n = event.get("total_parts")
                print(f"[{etype}] {stage} | stage {stage_prog:.1f}% | overall {overall:.1f}% (part {part_i}/{part_n})")

            elif etype == "error":
                # You will also get a raised exception after this yield
                print("[error]", event.get("error"), event.get("error_type"))

            elif etype == "finish":
                result = event["translate_result"]
                print("Done in", getattr(result, "total_seconds", None), "s")
                print("Mono:", getattr(result, "mono_pdf_path", None))
                print("Dual:", getattr(result, "dual_pdf_path", None))
                print("No-watermark Mono:", getattr(result, "no_watermark_mono_pdf_path", None))
                print("No-watermark Dual:", getattr(result, "no_watermark_dual_pdf_path", None))
                print("Glossary:", getattr(result, "auto_extracted_glossary_path", None))
                print("Peak memory:", getattr(result, "peak_memory_usage", None))
                break

    except Exception as exc:
        # Catch exceptions raised by the stream after an error event
        print("Translation failed:", exc)

# asyncio.run(translate_one(settings, "/path/to/file.pdf"))
```

### 取消
您可以取消消耗流式传输的任务。取消操作会传播到底层的翻译进程。

```python
import asyncio
from pdf2zh_next.high_level import do_translate_async_stream

async def cancellable(settings, pdf):
    task = asyncio.create_task(_consume(settings, pdf))
    await asyncio.sleep(1.0)  # let it start
    task.cancel()
    try:
        await task
    except asyncio.CancelledError:
        print("Cancelled")

async def _consume(settings, pdf):
    async for event in do_translate_async_stream(settings, pdf):
        if event["type"] == "finish":
            break
```

### 示例事件形状
阶段摘要事件(示例):
```json
{
  "type": "stage_summary",
  "stages": [
    {"name": "Parse PDF and Create Intermediate Representation", "percent": 0.1086},
    {"name": "DetectScannedFile", "percent": 0.0188},
    {"name": "Parse Page Layout", "percent": 0.1079}
    // ... more stages ...
  ],
  "part_index": 0,
  "total_parts": 1
}
```

Progress event (example):
```json
{
  "type": "progress_update",
  "stage": "Translate Paragraphs",
  "stage_progress": 2.04,
  "stage_current": 1,
  "stage_total": 49,
  "overall_progress": 53.44,
  "part_index": 1,
  "total_parts": 1
}
```

完成事件(示例):
```json
{
  "type": "finish",
  "translate_result": {
    "original_pdf_path": "pdf2zh_files/<session>/table.pdf",
    "mono_pdf_path": "pdf2zh_files/<session>/table.zh-CN.mono.pdf",
    "dual_pdf_path": "pdf2zh_files/<session>/table.zh-CN.dual.pdf",
    "no_watermark_mono_pdf_path": "pdf2zh_files/<session>/table.no_watermark.zh-CN.mono.pdf",
    "no_watermark_dual_pdf_path": "pdf2zh_files/<session>/table.no_watermark.zh-CN.dual.pdf",
    "auto_extracted_glossary_path": "pdf2zh_files/<session>/table.zh-CN.glossary.csv",
    "total_seconds": 42.83,
    "peak_memory_usage": 4651.55
  }
}
```

错误事件(示例):
```json
{
  "type": "error",
  "error": "Babeldoc translation error: <message>",
  "error_type": "BabeldocError",
  "details": "<optional original error or traceback>"
}
```

### 注意事项与最佳实践
- 始终处理来自生成器的错误事件和异常。
- 在 `finish` 事件时中断循环,以避免不必要的工作。
- 在调用前确保 `file` 存在且 `settings.validate_settings()` 通过验证。
- 大型文档可能会被拆分;使用 `part_index/total_parts` 和 `overall_progress` 来驱动您的用户界面。
- 如果您的用户界面对重复、相同的更新敏感,请对 `progress_update` 进行防抖处理。
- `report_interval` (SettingsModel):仅控制 `progress_update` 事件的发射频率。它不影响 `stage_summary`、`progress_start`、`progress_end` 或 `finish`。默认值为 0.1 秒,允许的最小值为 0.05 秒。根据进度监视器逻辑,当 `stage_total <= 3` 时,更新不受 `report_interval` 的限制。

<div align="right"> 
<h6><small>本页面的部分内容由 GPT 翻译,可能包含错误。</small></h6>