|
| 1 | +# 教程:下载后转为 PDF / ZIP / 长图 |
| 2 | + |
| 3 | +## 1. 需求场景 |
| 4 | + |
| 5 | +下载本子后,很多用户有进一步导出的需求: |
| 6 | +- 导出为 **PDF**:方便在电子阅读器上查看 |
| 7 | +- 导出为 **ZIP**:方便传输和存档 |
| 8 | +- 合并为 **长图**:方便一张图看完整个章节 |
| 9 | + |
| 10 | +jmcomic 内置了三个开箱即用的导出 Feature,对应这三种需求: |
| 11 | + |
| 12 | +| Feature | 效果 | |
| 13 | +|---------|------| |
| 14 | +| `Feature.export_pdf` | 下载完自动导出为 PDF | |
| 15 | +| `Feature.export_zip` | 下载完自动打包为 ZIP | |
| 16 | +| `Feature.export_long_img` | 下载完自动拼接为长图 PNG | |
| 17 | + |
| 18 | + |
| 19 | +> 也许你知道,这些功能之前是以插件形式 (JmOptionPlugin) 存在的。 |
| 20 | +> |
| 21 | +> 是的,传统方式需要在 option 配置文件中编写插件配置,门槛偏高。 |
| 22 | +> |
| 23 | +> 因此,从v2.6.19起,jmcomic 引入了上述的 **Feature** 机制,尽可能简化这些最常用的功能,让小白也能用一行代码搞定导出。 |
| 24 | +
|
| 25 | + |
| 26 | +## 2. 快速上手 |
| 27 | + |
| 28 | +### 2.1 导出 PDF——基本用法示例 |
| 29 | + |
| 30 | +```python |
| 31 | +from jmcomic import download_album, Feature |
| 32 | + |
| 33 | +# 只需要加一个 extra 参数,就能在下载完成后自动导出 PDF |
| 34 | +download_album('123', extra=Feature.export_pdf) |
| 35 | + |
| 36 | +# 如果要传 option 参数,就是如下写法,三个参数 |
| 37 | +download_album('123', option, extra=Feature.export_pdf) |
| 38 | +``` |
| 39 | + |
| 40 | +**效果**:在本子下载完以后,默认在**下载根目录**下生成包含所有本子图片的 PDF 文件。如果你没有自定义过option,下载根目录就是你的工作目录(即你运行python脚本或cli的目录)。如果你配置过option,会放在dir_rule.base_dir下面。 |
| 41 | + |
| 42 | +```text |
| 43 | +./ |
| 44 | +├── [JM123]本子标题.pdf ← 整本合并为 1 个 PDF,注意pdf文件名的格式,默认包含本子禁漫车号+本子标题 |
| 45 | +``` |
| 46 | + |
| 47 | +### 2.2 需要多种导出格式(PDF、ZIP等)——直接组合 Feature |
| 48 | + |
| 49 | +用 `+` 号组合,同时导出多种格式: |
| 50 | + |
| 51 | +```python |
| 52 | +# 下载完后同时导出 PDF 和 ZIP |
| 53 | +download_album('123', option, extra=Feature.export_pdf + Feature.export_zip) |
| 54 | + |
| 55 | +# 也支持列表语法,|语法 |
| 56 | +download_album('123', option, extra=[Feature.export_pdf, Feature.export_zip]) |
| 57 | +download_album('123', option, extra=Feature.export_pdf | Feature.export_zip) |
| 58 | +``` |
| 59 | + |
| 60 | +效果同pdf,会在本子下载完以后,额外在对应的下载目录下,生成包含所有本子图片的 PDF 文件和 ZIP 文件: |
| 61 | + |
| 62 | +```text |
| 63 | +./ |
| 64 | +├── [JM123]本子标题.pdf ← 整本合并为 1 个 PDF |
| 65 | +├── [JM123]本子标题.zip ← 整本合并为 1 个 zip 压缩包 |
| 66 | +``` |
| 67 | + |
| 68 | + |
| 69 | +### 2.3 自定义参数 |
| 70 | + |
| 71 | +如果你了解插件配置,可以同样使用Feature传递插件的自定义参数,例如改变输出目录、命名规则等: |
| 72 | + |
| 73 | +```python |
| 74 | +# 示例 1:指定输出目录和命名规则 |
| 75 | +download_album('123', option, extra=Feature.export_pdf( |
| 76 | + # 下面是自定义参数 |
| 77 | + pdf_dir='D:/my_pdfs', # PDF 保存到 D:/my_pdfs 文件夹 |
| 78 | + filename_rule='Atitle', # 用本子标题作为文件名 |
| 79 | + delete_original_file=True, # 合并完 PDF 后删除原图 |
| 80 | +)) |
| 81 | +``` |
| 82 | + |
| 83 | +> 💡 **小白必读:命名规则(filename_rule)的小知识** |
| 84 | +> - `A` 开头的占位符(如 `Atitle`, `Aid`)代表 **Album (本子)**。 |
| 85 | +> - `P` 开头的占位符(如 `Ptitle`, `Pid`)代表 **Photo (章节)**。 |
| 86 | +> - `download_photo` (下载单章)时,由于程序既知道当前章节,也知道它属于哪个本子,所以 **`Pxxx` 和 `Axxx` 都可以用**。 |
| 87 | +> - `download_album` (下载整本)时,由于是按本子合并的,程序没有具体的“当前章节”,此时 **只能用 `Axxx`,不能用 `Pxxx`**,否则会报错。 |
| 88 | +
|
| 89 | +```python |
| 90 | +# 示例 2:全都要——ZIP 存盘 + 长图阅读 |
| 91 | +combo = ( |
| 92 | + Feature.export_zip(zip_dir='D:/zips') |
| 93 | + + Feature.export_long_img(img_dir='D:/long_imgs') |
| 94 | +) |
| 95 | +download_album('123', option, extra=combo) |
| 96 | +``` |
| 97 | + |
| 98 | +### 2.4 download_photo 也支持 |
| 99 | + |
| 100 | +```python |
| 101 | +from jmcomic import download_photo, Feature |
| 102 | + |
| 103 | +# 对单个章节导出 |
| 104 | +download_photo('456', option, extra=Feature.export_pdf) |
| 105 | +``` |
| 106 | + |
| 107 | +效果:在对应的下载目录下生成以章节标题命名的 PDF: |
| 108 | + |
| 109 | +```text |
| 110 | +./ |
| 111 | +├── [JM{Pid}]章节标题.pdf ← 该章节导出为 1 个 PDF |
| 112 | +``` |
| 113 | + |
| 114 | +> 💡 **提示**:同一个 Feature,通过 `download_album` 和 `download_photo` 调用时会自动适配不同的导出行为,详见下方 [智能适配规则](#25-智能适配规则)。 |
| 115 | +
|
| 116 | +### 2.5 智能适配规则 |
| 117 | + |
| 118 | +内置的导出 Feature 会根据调用的 API **自动适配**参数: |
| 119 | + |
| 120 | +| 调用方式 | Feature.export_pdf | Feature.export_zip | Feature.export_long_img | |
| 121 | +|-----------------|-------------------|-------------------|----------------------| |
| 122 | +| `download_album` | 整本合并为 1 个 PDF<br>`[JM本子号]本子标题.pdf` | 整本打包为 1 个 ZIP<br>`[JM本子号]本子标题.zip` | 所有章节合并为 1 张长图<br>`[JM本子号]本子标题.png` | |
| 123 | +| `download_photo` | 该章节导出为 PDF<br>`[JM章节号]章节标题.pdf` | 该章节打包为 ZIP<br>`[JM章节号]章节标题.zip` | 该章节拼接为长图<br>`[JM章节号]章节标题.png` | |
| 124 | + |
| 125 | +当你显式传入参数时(如 `filename_rule='Ptitle'`),**你的配置优先**,不会被自适应覆盖。 |
| 126 | + |
| 127 | +> 💡 **提示**:更多可选参数(如加密密码 `encrypt`、后缀名 `suffix` 等),参考 [Plugin 插件参数大全](../option_file_syntax.md#3-option插件配置项)。 |
| 128 | +
|
| 129 | +## 3. 传统写法(YAML 插件配置) |
| 130 | + |
| 131 | +如果你更习惯配置文件,仍然可以使用传统的插件配置方式: |
| 132 | + |
| 133 | +```yaml |
| 134 | +# option.yml |
| 135 | +plugins: |
| 136 | + after_album: # 整本下载完以后 |
| 137 | + - plugin: img2pdf # 合并pdf |
| 138 | + kwargs: |
| 139 | + pdf_dir: ./output |
| 140 | + filename_rule: Atitle |
| 141 | + - plugin: zip # 合并为压缩文件 |
| 142 | + kwargs: |
| 143 | + level: album |
| 144 | + zip_dir: ./output |
| 145 | +``` |
| 146 | +
|
| 147 | +传统写法的更多细节见 → [Plugin 插件教程](./6_plugin.md) |
| 148 | +
|
| 149 | +## 4. Feature 架构设计 |
| 150 | +
|
| 151 | +### 类层次 |
| 152 | +
|
| 153 | +```text |
| 154 | +Feature (基类) |
| 155 | + ├── PluginFeature ← 封装插件调用,参数根据来源自适应 |
| 156 | + └── 你的自定义 Feature ← 继承 Feature,实现任意逻辑 |
| 157 | +``` |
| 158 | + |
| 159 | +- **Feature 基类**:通用的附加行为抽象,不绑定任何具体实现。默认在所有生命周期钩子中执行。 |
| 160 | +- **PluginFeature**:Feature 的子类,专门封装 jmcomic 插件。除了调用插件之外,还会根据调用来源动态适配 `filename_rule` 参数;ZIP 的打包粒度则由插件在运行时根据上下文自动推导。 |
| 161 | + |
| 162 | +### 执行流程 |
| 163 | + |
| 164 | +Feature **自然嵌入到 downloader 的生命周期钩子**中自动触发: |
| 165 | + |
| 166 | +```text |
| 167 | +api.download_album(extra=Feature.export_pdf) |
| 168 | + │ |
| 169 | + ├→ dler.add_features(pdf, 'download_album') # 注册: [(pdf, 'download_album')] |
| 170 | + │ |
| 171 | + └→ dler.download_album(id) |
| 172 | + │ |
| 173 | + ├→ before_album(album) |
| 174 | + │ |
| 175 | + ├→ download_by_photo_detail(photo) |
| 176 | + │ ├→ before_photo(photo) |
| 177 | + │ ├→ download jmcomic images ... # 下载禁漫图片 |
| 178 | + │ └→ after_photo(photo) |
| 179 | + │ └→ _invoke_features_for('after_photo') |
| 180 | + │ └→ pdf.should_invoke('after_photo', 'download_album') → False ✗ 跳过 |
| 181 | + │ |
| 182 | + └→ after_album(album) |
| 183 | + └→ _invoke_features_for('after_album') |
| 184 | + └→ pdf.should_invoke('after_album', 'download_album') → True ✓ 执行! |
| 185 | + └→ _adapt_plugin_kwargs(from, when) # 动态生成插件参数 |
| 186 | + └→ option.invoke(pdf, kwargs) # 调用pdf插件,传入参数 |
| 187 | +``` |
| 188 | + |
| 189 | +> 💡 **关键点**: |
| 190 | +> |
| 191 | +> - **执行时机**:`PluginFeature` 根据注册来源自动推导(`download_album` → `after_album`,`download_photo` → `after_photo`)。自定义 Feature 默认在所有钩子都会执行,你可以覆写 `should_invoke` 来控制。 |
| 192 | +> - **参数自适应**:`PluginFeature` 的 `filename_rule` 前缀(A/P)会根据来源动态适配。ZIP 的打包粒度由插件根据上下文自动推导。用户显式传入的参数不会被覆盖。 |
| 193 | +
|
| 194 | +### 自定义 Feature |
| 195 | + |
| 196 | +Feature 基类完全不绑定插件,你可以实现任意逻辑,欢迎贡献你的feature到本项目中: |
| 197 | + |
| 198 | +```python |
| 199 | +from jmcomic import Feature, download_album |
| 200 | + |
| 201 | +class NotifyFeature(Feature): |
| 202 | + """下载完成后发送通知""" |
| 203 | + def invoke(self, option, **kwargs): |
| 204 | + album = kwargs.get('album') |
| 205 | + if album: |
| 206 | + print(f'下载完成通知: {album.name}') |
| 207 | + |
| 208 | +# 使用 |
| 209 | +download_album('123', option, extra=NotifyFeature()) |
| 210 | +``` |
| 211 | + |
0 commit comments