jscmp4 commited on
Commit
1b29f3e
·
verified ·
1 Parent(s): 80ac209

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +27 -304
app.py CHANGED
@@ -1,336 +1,59 @@
1
  import gradio as gr
2
- import os
3
- import shutil
4
- import yaml
5
- import logging
6
- import io
7
- import re
8
- import img2pdf
9
- import subprocess # 用于调用 gallery-dl
10
- from jmcomic import create_option, JmcomicException
11
-
12
- # =================配置区域=================
13
- BASE_DIR = "download_cache"
14
- TEMP_YML = "jm_option_temp.yml"
15
-
16
- # --- 日志捕获设置 (专用于 JMComic) ---
17
- log_capture_string = io.StringIO()
18
- ch = logging.StreamHandler(log_capture_string)
19
- ch.setLevel(logging.INFO)
20
- logger = logging.getLogger('jmcomic')
21
- logger.addHandler(ch)
22
- logger.setLevel(logging.INFO)
23
-
24
- # =================通用工具函数=================
25
-
26
- # 1. 自然排序:让 1.jpg, 2.jpg, 10.jpg 正确排序
27
- def natural_sort_key(s):
28
- return [int(text) if text.isdigit() else text.lower()
29
- for text in re.split(r'(\d+)', s)]
30
-
31
- # 2. 清理文件名:去除非法字符
32
- def sanitize_filename(name):
33
- return re.sub(r'[\\/*?:"<>|]', '_', name)
34
-
35
- # 3. 手动合并 PDF (核心通用功能)
36
- def manual_merge_pdf(album_dir, output_pdf_path):
37
- image_paths = []
38
- # 递归查找所有图片
39
- for root, _, files in os.walk(album_dir):
40
- for file in files:
41
- if file.lower().endswith(('.jpg', '.jpeg', '.png', '.webp')):
42
- image_paths.append(os.path.join(root, file))
43
-
44
- if not image_paths:
45
- raise Exception("目录中未找到任何图片")
46
-
47
- # 按文件名自然排序
48
- image_paths.sort(key=natural_sort_key)
49
-
50
- # 转换为 PDF
51
- with open(output_pdf_path, "wb") as f:
52
- f.write(img2pdf.convert(image_paths))
53
-
54
- # =================逻辑模块 1: JMComic 下载=================
55
-
56
- def run_jm_download(album_ids_str, auth_type, cookies_input, username_input, password_input):
57
- # 1. 解析 ID 列表
58
- id_list = [x.strip() for x in re.split(r'[\s,,\n]+', album_ids_str) if x.strip()]
59
-
60
- if not id_list:
61
- yield None, "❌ 错误: 请至少输入一个本子 ID", "未检测到 ID"
62
- return
63
-
64
- # 2. 初始化环境
65
- log_capture_string.truncate(0)
66
- log_capture_string.seek(0)
67
-
68
- # PDF 存放专用目录
69
- pdf_final_dir = os.path.join(BASE_DIR, "final_pdfs")
70
- os.makedirs(pdf_final_dir, exist_ok=True)
71
-
72
- # 原始图片临时目录
73
- raw_download_dir = os.path.join(BASE_DIR, "raw_jm")
74
-
75
- # 3. 生成基础配置
76
- config = {
77
- 'client': {'impl': 'api', 'retry_times': 3},
78
- 'download': {
79
- 'image': {'suffix': '.jpg', 'quality': 85},
80
- 'threading': {'batch_count': 5}
81
- },
82
- 'dir_rule': {'base_dir': raw_download_dir},
83
- }
84
-
85
- if auth_type == "使用账号密码 (推荐)":
86
- if not username_input or not password_input:
87
- yield None, "❌ 错误: 请填写账号和密码", "参数错误"
88
- return
89
- config['client']['username'] = username_input
90
- config['client']['password'] = password_input
91
-
92
- with open(TEMP_YML, 'w', encoding='utf-8') as f:
93
- yaml.dump(config, f)
94
-
95
- # 4. 创建 Option
96
- try:
97
- option = create_option(TEMP_YML)
98
- if auth_type == "使用 Cookies (手动)" and cookies_input:
99
- option.call_once()
100
- option.headers['cookie'] = cookies_input.strip()
101
- except Exception as e:
102
- yield None, f"❌ 配置初始化失败: {e}", "初始化错误"
103
- return
104
-
105
- # 5. 循环下载
106
- success_files = []
107
- total_count = len(id_list)
108
-
109
- for index, album_id in enumerate(id_list):
110
- current_num = index + 1
111
- progress_str = f"({current_num}/{total_count})"
112
-
113
- # 清理旧图片
114
- if os.path.exists(raw_download_dir):
115
- shutil.rmtree(raw_download_dir)
116
- os.makedirs(raw_download_dir, exist_ok=True)
117
-
118
- yield success_files, f"🔄 {progress_str} 正在处理 ID: {album_id} ...", f"处理中 {current_num}/{total_count}"
119
-
120
- real_title = f"Unknown_{album_id}"
121
-
122
- try:
123
- # A. 获取信息
124
- client = option.build_jm_client()
125
- try:
126
- album_detail = client.get_album_detail(album_id)
127
- real_title = album_detail.title
128
- except Exception as e:
129
- logger.error(f"ID {album_id} 获取详情失败: {e}")
130
- continue
131
-
132
- # B. 下载
133
- yield success_files, f"📥 {progress_str} 正在下载图片: {real_title}...", f"下载中 {current_num}/{total_count}"
134
- option.download_album(album_id)
135
-
136
- # C. 合并
137
- yield success_files, f"🔨 {progress_str} 正在合并 PDF...", f"合并中 {current_num}/{total_count}"
138
-
139
- safe_title = sanitize_filename(real_title)
140
- final_pdf_name = f"[JM] {safe_title}.pdf" # 加个前缀区分
141
- final_pdf_path = os.path.join(pdf_final_dir, final_pdf_name)
142
-
143
- try:
144
- manual_merge_pdf(raw_download_dir, final_pdf_path)
145
- except Exception as e:
146
- logger.error(f"合并PDF失败: {e}")
147
- yield success_files, f"❌ {progress_str} 合并失败: {e}", "合并出错"
148
- continue
149
-
150
- success_files.append(final_pdf_path)
151
- yield success_files, f"✅ {progress_str} 完成: {real_title}", f"完成 {current_num}/{total_count}"
152
-
153
- except Exception as e:
154
- logger.error(f"ID {album_id} 未知错误: {e}")
155
- import traceback
156
- traceback.print_exc()
157
- continue
158
-
159
- full_log = log_capture_string.getvalue()
160
- if not success_files:
161
- yield None, f"❌ 任务结束,无成功文件。\n日志:\n{full_log[-1000:]}", "全部失败"
162
- else:
163
- yield success_files, f"🎉 全部处理完毕!成功: {len(success_files)}个", "全部完成"
164
-
165
- # =================逻辑模块 2: E-Hentai 下载=================
166
-
167
- def run_eh_download(eh_url, cookies_str):
168
- # 1. 检查参数
169
- if not eh_url:
170
- yield None, "❌ 请输入画廊链接", "参数缺失"
171
- return
172
-
173
- # 2. 目录准备
174
- eh_base_dir = os.path.join(BASE_DIR, "eh_temp")
175
- if os.path.exists(eh_base_dir):
176
- try:
177
- shutil.rmtree(eh_base_dir)
178
- except:
179
- pass
180
- os.makedirs(eh_base_dir, exist_ok=True)
181
-
182
- pdf_final_dir = os.path.join(BASE_DIR, "final_pdfs")
183
- os.makedirs(pdf_final_dir, exist_ok=True)
184
-
185
- # 3. 处理 Cookies
186
- cookie_file_path = os.path.join(BASE_DIR, "eh_cookies.txt")
187
- with open(cookie_file_path, "w", encoding="utf-8") as f:
188
- f.write(cookies_str)
189
-
190
- yield None, "🚀 启动 gallery-dl 解析...", "开始解析"
191
-
192
- # 4. 构建命令
193
- cmd = [
194
- "gallery-dl",
195
- "--directory", eh_base_dir,
196
- "--cookies", cookie_file_path,
197
- eh_url
198
- ]
199
-
200
- # 5. 执行下载
201
- try:
202
- process = subprocess.Popen(
203
- cmd,
204
- stdout=subprocess.PIPE,
205
- stderr=subprocess.STDOUT,
206
- text=True
207
- )
208
-
209
- logs = ""
210
- for line in process.stdout:
211
- logs += line
212
- if "Example" in line or "http" in line or "#" in line:
213
- # 简单的日志过滤,避免刷屏
214
- yield None, f"📥 下载中...\n{line.strip()}", "下载中"
215
-
216
- process.wait()
217
-
218
- if process.returncode != 0:
219
- yield None, f"❌ 下载失败,请检查 Cookies 或链接。\n日志片段:\n{logs[-500:]}", "下载失败"
220
- return
221
-
222
- except Exception as e:
223
- yield None, f"❌ 调用出错: {e}", "系统错误"
224
- return
225
-
226
- # 6. 寻找图片目录
227
- target_img_dir = None
228
- for root, dirs, files in os.walk(eh_base_dir):
229
- if any(f.lower().endswith(('.jpg', '.png', '.jpeg')) for f in files):
230
- target_img_dir = root
231
- break
232
-
233
- if not target_img_dir:
234
- yield None, "❌ 未找到下载的图片 (可能是 Sad Panda 问题)", "未找到图片"
235
- return
236
-
237
- # 7. 合并 PDF
238
- folder_name = os.path.basename(target_img_dir)
239
- safe_title = sanitize_filename(folder_name)
240
- final_pdf_name = f"[EH] {safe_title}.pdf"
241
- final_pdf_path = os.path.join(pdf_final_dir, final_pdf_name)
242
-
243
- yield None, "🔨 正在合并 PDF...", "正在合并"
244
- try:
245
- manual_merge_pdf(target_img_dir, final_pdf_path)
246
- except Exception as e:
247
- yield None, f"❌ 合并失败: {e}", "合并失败"
248
- return
249
-
250
- yield [final_pdf_path], f"✅ 处理完成!\n文件: {final_pdf_name}", "完成"
251
-
252
- # =================界面构建 (UI)=================
253
 
 
254
  with gr.Blocks(title="二次元聚合下载器") as demo:
255
  gr.Markdown("## 📦 二次元聚合下载器 (JMComic + E-Hentai)")
256
- gr.Markdown("自动下载图片并合并为 PDF。支持多任务队列。")
257
 
258
  with gr.Tabs():
259
 
260
- # ---------------- Tab 1: JMComic ----------------
261
  with gr.TabItem("🦄 JMComic (ID下载)"):
262
- gr.Markdown("一行一个 ID,下载完一个才会开始下一个,稳定防崩。")
263
-
264
  with gr.Row():
265
- jm_inp_ids = gr.Textbox(label="输入本子 ID 列表 (回车分隔)", placeholder="123456\n234567", lines=5)
266
 
267
  with gr.Row():
268
- jm_auth_select = gr.Radio(
269
- ["使用账号密码 (推荐)", "使用 Cookies (手动)"],
270
- label="登录方式",
271
- value="使用账号密码 (推荐)"
272
- )
273
 
274
  with gr.Group(visible=True) as group_user:
275
  with gr.Row():
276
- jm_inp_user = gr.Textbox(label="用户名", placeholder="输入你的账号")
277
- jm_inp_pass = gr.Textbox(label="密码", type="password", placeholder="输入你的密码")
278
-
279
  with gr.Group(visible=False) as group_cookie:
280
- jm_inp_cookie = gr.Textbox(label="Cookies", placeholder="key=value...")
281
 
282
- # 切换显示逻辑
283
  def toggle_auth(choice):
284
- if choice == "使用账号密码 (推荐)":
285
- return gr.update(visible=True), gr.update(visible=False)
286
- else:
287
- return gr.update(visible=False), gr.update(visible=True)
288
 
289
  jm_auth_select.change(fn=toggle_auth, inputs=jm_auth_select, outputs=[group_user, group_cookie])
290
 
291
- jm_btn_run = gr.Button("🚀 JM 批量下载", variant="primary")
292
-
293
  with gr.Row():
294
- jm_out_badge = gr.Label(value="等待指令", label="总体进度")
295
- jm_out_log = gr.Textbox(label="实时日志", lines=5)
296
-
297
- jm_out_files = gr.File(label="下载结果", file_count="multiple")
298
 
299
- jm_btn_run.click(
300
- fn=run_jm_download,
301
- inputs=[jm_inp_ids, jm_auth_select, jm_inp_cookie, jm_inp_user, jm_inp_pass],
302
- outputs=[jm_out_files, jm_out_log, jm_out_badge]
303
- )
304
 
305
- # ---------------- Tab 2: E-Hentai ----------------
306
  with gr.TabItem("🐼 E-Hentai (链接下载)"):
307
- gr.Markdown("输入 E站/EX站 画廊链接。**必须提供 Cookies** (Netscape格式) 以避免 Sad Panda。")
308
-
309
  with gr.Row():
310
- eh_inp_url = gr.Textbox(label="画廊链接 (URL)", placeholder="https://e-hentai.org/g/xxxxx/yyyy/")
311
-
312
  with gr.Row():
313
- eh_inp_cookies = gr.Textbox(
314
- label="Cookies (Netscape 格式)",
315
- placeholder="# Netscape HTTP Cookie File\n.e-hentai.org\tTRUE\t/ ...",
316
- lines=5,
317
- info="请使用 Chrome 插件 'Get cookies.txt LOCALLY' 导出并在粘贴至此。"
318
- )
319
-
320
- eh_btn_run = gr.Button("🚀 EH 下载并转 PDF", variant="primary")
321
 
 
322
  with gr.Row():
323
- eh_out_badge = gr.Label(value="等待指令", label="状态")
324
- eh_out_log = gr.Textbox(label="运行日志", lines=5)
325
-
326
- eh_out_files = gr.File(label="下载结果")
327
 
328
- eh_btn_run.click(
329
- fn=run_eh_download,
330
- inputs=[eh_inp_url, eh_inp_cookies],
331
- outputs=[eh_out_files, eh_out_log, eh_out_badge]
332
- )
333
 
334
  if __name__ == "__main__":
335
- # 使用 queue() 确保长任务不会超时断开
336
  demo.queue().launch()
 
1
  import gradio as gr
2
+ from jm_logic import run_jm_download
3
+ from eh_logic import run_eh_download
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
 
5
+ # --- 界面构建 ---
6
  with gr.Blocks(title="二次元聚合下载器") as demo:
7
  gr.Markdown("## 📦 二次元聚合下载器 (JMComic + E-Hentai)")
8
+ gr.Markdown("模块化重构版:支持多平台下载 PDF 自动合并。")
9
 
10
  with gr.Tabs():
11
 
12
+ # === JMComic Tab ===
13
  with gr.TabItem("🦄 JMComic (ID下载)"):
 
 
14
  with gr.Row():
15
+ jm_inp_ids = gr.Textbox(label="输入本子 ID 列表 (回车分隔)", placeholder="438696\n123456", lines=5)
16
 
17
  with gr.Row():
18
+ jm_auth_select = gr.Radio(["使用账号密码 (推荐)", "使用 Cookies (手动)"], label="登录方式", value="使用账号密码 (推荐)")
 
 
 
 
19
 
20
  with gr.Group(visible=True) as group_user:
21
  with gr.Row():
22
+ jm_inp_user = gr.Textbox(label="用户名")
23
+ jm_inp_pass = gr.Textbox(label="密码", type="password")
 
24
  with gr.Group(visible=False) as group_cookie:
25
+ jm_inp_cookie = gr.Textbox(label="Cookies")
26
 
 
27
  def toggle_auth(choice):
28
+ return (gr.update(visible=True), gr.update(visible=False)) if choice == "使用账号密码 (推荐)" else (gr.update(visible=False), gr.update(visible=True))
 
 
 
29
 
30
  jm_auth_select.change(fn=toggle_auth, inputs=jm_auth_select, outputs=[group_user, group_cookie])
31
 
32
+ jm_btn = gr.Button("🚀 开始下载", variant="primary")
 
33
  with gr.Row():
34
+ jm_badge = gr.Label(value="Ready", label="状态")
35
+ jm_log = gr.Textbox(label="日志", lines=5)
36
+ jm_files = gr.File(label="结果")
 
37
 
38
+ jm_btn.click(fn=run_jm_download,
39
+ inputs=[jm_inp_ids, jm_auth_select, jm_inp_cookie, jm_inp_user, jm_inp_pass],
40
+ outputs=[jm_files, jm_log, jm_badge])
 
 
41
 
42
+ # === E-Hentai Tab ===
43
  with gr.TabItem("🐼 E-Hentai (链接下载)"):
44
+ gr.Markdown("**提示**:必须填写 Cookies (Netscape格式) 才能下载 EX 站或原图。")
 
45
  with gr.Row():
46
+ eh_url = gr.Textbox(label="画廊链接")
 
47
  with gr.Row():
48
+ eh_cookie = gr.Textbox(label="Cookies", lines=5, placeholder="# Netscape HTTP Cookie File...")
 
 
 
 
 
 
 
49
 
50
+ eh_btn = gr.Button("🚀 开始下载", variant="primary")
51
  with gr.Row():
52
+ eh_badge = gr.Label(value="Ready", label="状态")
53
+ eh_log = gr.Textbox(label="日志", lines=5)
54
+ eh_files = gr.File(label="结果")
 
55
 
56
+ eh_btn.click(fn=run_eh_download, inputs=[eh_url, eh_cookie], outputs=[eh_files, eh_log, eh_badge])
 
 
 
 
57
 
58
  if __name__ == "__main__":
 
59
  demo.queue().launch()