', str(text))
192 | if len(match) > 0:
193 | warn_element = text.find(f'p{match[0]}')
194 | warn_element.decompose()
195 |
196 |
197 | text = text.decode_contents()
198 | if text.startswith('\n'):
199 | text = text[1:]
200 | if text.endswith('\n\n'):
201 | text = text[:-1]
202 |
203 | msg = '
————————————以下为告示,读者请无视——————————————
'
204 | text = text[:text.find(msg)]
205 |
206 | #去除乱码
207 | if is_tansfer_rubbish_code:
208 | text = replace_rubbish_text(text)
209 | return text
210 |
211 | def remove_element(self, bf_item, id=None, class_=None):
212 | if id is not None:
213 | remove_list = bf_item.find_all(id=id)
214 | elif class_ is not None:
215 | remove_list = bf_item.find_all(class_=class_)
216 | for remove_element in remove_list:
217 | remove_element.decompose()
218 |
219 | def get_chap_text(self, url, chap_name, return_next_chapter=False):
220 | text_chap = ''
221 | page_no = 1
222 | url_ori = url
223 | next_chap_url = None
224 | while True:
225 | if page_no == 1:
226 | str_out = chap_name
227 | else:
228 | str_out = f' 正在下载第{page_no}页......'
229 | print(str_out)
230 | content_html = self.get_html(url, is_gbk=False)
231 | text = self.get_page_text(content_html)
232 | text_chap += text
233 | url_new = url_ori.replace('.html', '_{}.html'.format(page_no+1))[len(self.url_head):]
234 | if url_new in content_html:
235 | page_no += 1
236 | url = self.url_head + url_new
237 | else:
238 | if return_next_chapter:
239 | next_chap_url = self.url_head + re.search(r'书签下一页', content_html).group(1)
240 | break
241 | return text_chap, next_chap_url
242 |
243 | def get_text(self):
244 | self.make_folder()
245 | repeat_img_strs = [] #记录重复的图片
246 | text_no=0 #text_no正文章节编号(排除插图) chap_no 是所有章节编号
247 | for chap_no, (chap_name, chap_url) in enumerate(zip(self.volume['chap_names'], self.volume['chap_urls'])):
248 | is_fix_next_chap_url = (chap_name in self.missing_last_chap_list)
249 | text, next_chap_url = self.get_chap_text(chap_url, chap_name, return_next_chapter=is_fix_next_chap_url)
250 |
251 | if chap_name == self.color_chap_name:
252 | text_html_color = text2htmls(self.color_page_name, text)
253 | else:
254 | file_name = os.path.join(self.text_path, f'{str(text_no).zfill(2)}.xhtml')
255 | text_html = text2htmls(chap_name, text)
256 | text_no += 1
257 | with open(file_name, 'w+', encoding='utf-8') as f:
258 | f.write(text_html)
259 | repeat_img_strs += re.findall(r'
', text_html)
260 |
261 | if is_fix_next_chap_url:
262 | self.volume['chap_urls'][chap_no+1] = next_chap_url #正向修复
263 |
264 | # 将彩页中后文已经出现的图片删除,避免重复
265 | if self.is_color_page: #判断彩页是否存在
266 | text_html_color_new = []
267 | textfile = self.text_path + '/color.xhtml'
268 | for img_line in repeat_img_strs:
269 | if img_line in text_html_color:
270 | text_html_color = text_html_color.replace(img_line+'\n', '')
271 |
272 | with open(textfile, 'w+', encoding='utf-8') as f:
273 | f.write(text_html_color)
274 |
275 | def get_image(self, is_gui=False, signal=None):
276 | for url in self.img_url_map.keys():
277 | self.pool.submit(self.get_html_content, url)
278 | img_path = self.img_path
279 | if is_gui:
280 | len_iter = len(self.img_url_map.items())
281 | signal.emit('start')
282 | for i, (img_url, img_name) in enumerate(self.img_url_map.items()):
283 | content = self.get_html_content(img_url, is_buffer=True)
284 | with open(img_path+f'/{img_name}.jpg', 'wb') as f:
285 | f.write(content) #写入二进制内容
286 | signal.emit(int(100*(i+1)/len_iter))
287 | signal.emit('end')
288 | else:
289 | for img_url, img_name in tqdm(self.img_url_map.items()):
290 | content = self.get_html_content(img_url)
291 | with open(img_path+f'/{img_name}.jpg', 'wb') as f:
292 | f.write(content) #写入二进制内容
293 |
294 | def get_cover(self, is_gui=False, signal=None):
295 | img_w, img_h = 300, 300
296 | try:
297 | imgfile = os.path.join(self.img_path, '00.jpg')
298 | img = Image.open(imgfile)
299 | img_w, img_h = img.size
300 | signal_msg = (imgfile, img_h, img_w)
301 | if is_gui:
302 | signal.emit(signal_msg)
303 | except Exception as e:
304 | print(e)
305 | print('没有封面图片,请自行用第三方EPUB编辑器手动添加封面')
306 | with open(os.path.join(self.text_path, 'cover.xhtml'), 'w+', encoding='utf-8') as f:
307 | f.write(get_cover_html(img_w, img_h))
308 |
309 | def check_volume(self, is_gui=False, signal=None, editline=None):
310 | chap_names = self.volume['chap_names']
311 | chap_num = len(self.volume['chap_names'])
312 | for chap_no, url in enumerate(self.volume['chap_urls']):
313 | if self.check_url(url):
314 | if not self.prev_fix_url(chap_no, chap_num): #先尝试反向递归修复
315 | if chap_no == 0: #第一个章节都反向修复失败 说明后面章节全部缺失,只能手动输入第一个章节,保证第一个章节一定有效
316 | self.volume['chap_urls'][0] = self.hand_in_url(chap_names[chap_no], is_gui, signal, editline)
317 | else: #其余章节反向修复失败 默认使用正向修复
318 | self.missing_last_chap_list.append(chap_names[chap_no-1])
319 |
320 | #没有检测到插图页,手动输入插图页标题
321 | if self.color_chap_name not in self.volume['chap_names']:
322 | self.color_chap_name = self.hand_in_color_page_name(is_gui, signal, editline)
323 | self.volume['color_chap_name'] = self.color_chap_name
324 |
325 | #没有彩页 但主页封面存在,将主页封面设为书籍封面
326 | if self.color_chap_name=='':
327 | self.is_color_page = False
328 | if not self.check_url(self.cover_url_back):
329 | self.img_url_map[self.cover_url_back] = str(len(self.img_url_map)).zfill(2)
330 | print('**************')
331 | print('提示:没有彩页,但主页封面存在,将使用主页的封面图片作为本卷图书封面')
332 | print('**************')
333 |
334 | def check_url(self, url):#当检测有问题返回True
335 | return ('javascript' in url or 'cid' in url)
336 |
337 | def get_prev_url(self, chap_no): #获取前一个章节的链接
338 | content_html = self.get_html(self.volume['chap_urls'][chap_no], is_gbk=False)
339 | next_url = self.url_head + re.search(r'
上一页', content_html).group(1)
340 | return next_url
341 |
342 | def prev_fix_url(self, chap_no, chap_num): #反向递归修复缺失链接(后修复前),若成功修复返回True,否则返回False
343 | if chap_no==chap_num-1: #最后一个章节直接选择不修复 返回False
344 | return False
345 | elif self.check_url(self.volume['chap_urls'][chap_no+1]):
346 | if self.prev_fix_url(chap_no+1, chap_num):
347 | self.volume['chap_urls'][chap_no] = self.get_prev_url(chap_no+1)
348 | return True
349 | else:
350 | return False
351 | else:
352 | self.volume['chap_urls'][chap_no] = self.get_prev_url(chap_no+1)
353 | return True
354 |
355 | def hand_in_msg(self, error_msg='', is_gui=False, signal=None, editline=None):
356 | if is_gui:
357 | print(error_msg)
358 | signal.emit('hang')
359 | time.sleep(1)
360 | while not editline.isHidden():
361 | time.sleep(1)
362 | content = editline.text()
363 | editline.clear()
364 | else:
365 | content = input(error_msg)
366 | return content
367 |
368 | def hand_in_url(self, chap_name, is_gui=False, signal=None, editline=None):
369 | error_msg = f'章节\"{chap_name}\"连接失效,请手动输入该章节链接(手机版“{self.url_head}”开头的链接):'
370 | return self.hand_in_msg(error_msg, is_gui, signal, editline)
371 |
372 | def hand_in_color_page_name(self, is_gui=False, signal=None, editline=None):
373 | if is_gui:
374 | error_msg = f'插图页面不存在,需要下拉选择插图页标题,若不需要插图页则保持本栏为空直接点确定:'
375 | editline.addItems(self.volume['chap_names'])
376 | editline.setCurrentIndex(-1)
377 | else:
378 | error_msg = f'插图页面不存在,需要手动输入插图页标题,若不需要插图页则不输入直接回车:'
379 | return self.hand_in_msg(error_msg, is_gui, signal, editline)
380 |
381 | def get_toc(self):
382 | if self.is_color_page:
383 | ind = self.volume["chap_names"].index(self.color_chap_name)
384 | self.volume["chap_names"].pop(ind)
385 | toc_htmls = get_toc_html(self.book_name, self.volume["chap_names"])
386 | with open(os.path.join(self.temp_path, 'OEBPS/toc.ncx'), 'w+', encoding='utf-8') as f:
387 | f.write(toc_htmls)
388 |
389 | def get_content(self):
390 | content_html = get_content_html(self.book_name, self.volume['volume_name'], self.volume_no, self.author, self.publisher, self.brief, self.tag_list, len(self.volume["chap_names"]), len(os.listdir(self.img_path)), self.is_color_page)
391 | with open(os.path.join(self.temp_path, 'OEBPS/content.opf'), 'w+', encoding='utf-8') as f:
392 | f.write(content_html)
393 |
394 | def get_epub_head(self):
395 | metainf_folder = os.path.join(self.temp_path, 'META-INF')
396 | os.makedirs(metainf_folder, exist_ok=True)
397 | with open(os.path.join(metainf_folder, 'container.xml'), 'w+', encoding='utf-8') as f:
398 | f.write(get_container_html())
399 | with open(os.path.join(self.temp_path, 'mimetype'), 'w+', encoding='utf-8') as f:
400 | f.write('application/epub+zip')
401 |
402 | def get_epub(self):
403 | epub_file = self.epub_path + '/' + check_chars(self.book_name + '-' + self.volume['volume_name']) + '.epub'
404 | with zipfile.ZipFile(epub_file, "w", zipfile.ZIP_DEFLATED) as zf:
405 | for dirpath, _, filenames in os.walk(self.temp_path):
406 | fpath = dirpath.replace(self.temp_path,'') #这一句很重要,不replace的话,就从根目录开始复制
407 | fpath = fpath and fpath + os.sep or ''
408 | for filename in filenames:
409 | zf.write(os.path.join(dirpath, filename), fpath+filename)
410 | self.temp_path_io.cleanup()
411 | return epub_file
412 |
413 | # def buffer(self):
414 | # filename = 'buffer.pkl'
415 | # filepath = os.path.join(self.temp_path, filename)
416 | # if os.path.isfile(filepath):
417 | # with open(filepath, 'rb') as f:
418 | # self.volume, self.img_url_map = pickle.load(f)
419 | # self.text_path = os.path.join(self.temp_path, 'OEBPS/Text')
420 | # os.makedirs(self.text_path, exist_ok=True)
421 | # self.img_path = os.path.join(self.temp_path, 'OEBPS/Images')
422 | # os.makedirs(self.img_path, exist_ok=True)
423 | # self.color_chap_name = self.volume['color_chap_name']
424 | # else:
425 | # with open(filepath, 'wb') as f:
426 | # pickle.dump((self.volume ,self.img_url_map), f)
427 |
428 | # def is_buffer(self):
429 | # filename = 'buffer.pkl'
430 | # filepath = os.path.join(self.temp_path, filename)
431 | # return os.path.isfile(filepath)
--------------------------------------------------------------------------------
/backend/bilinovel/bilinovel_router.py:
--------------------------------------------------------------------------------
1 |
2 | from .Editer import Editer
3 | from backend.bilinovel.utils import *
4 |
5 |
6 |
7 | def query_chaps(book_no):
8 | print('未输入卷号,将返回书籍目录信息......')
9 | editer = Editer(root_path='./out', book_no=book_no)
10 | print('--------------------------------')
11 | print(editer.book_name, editer.author)
12 | print('--------------------------------')
13 | editer.get_chap_list()
14 | print('--------------------------------')
15 | print('请输入所需要的卷号进行下载。')
16 |
17 | def download_single_volume(root_path,
18 | book_no,
19 | volume_no,
20 | interval,
21 | num_thread,
22 | is_gui=False,
23 | hang_signal=None,
24 | progressring_signal=None,
25 | cover_signal=None,
26 | edit_line_hang=None):
27 |
28 | editer = Editer(root_path=root_path, book_no=book_no, volume_no=volume_no, interval=interval, num_thread=num_thread)
29 | print('正在积极地获取书籍信息....')
30 | success = editer.get_index_url()
31 | if not success:
32 | print('书籍信息获取失败')
33 | return
34 | print(editer.book_name + '-' + editer.volume['volume_name'], editer.author)
35 | print('****************************')
36 | # if not editer.is_buffer():
37 | editer.check_volume(is_gui=is_gui, signal=hang_signal, editline=edit_line_hang)
38 | print('正在下载文本....')
39 | print('*********************')
40 | editer.get_text()
41 | print('*********************')
42 | # editer.buffer()
43 | # else:
44 | # print('检测到文本文件,直接下载插图')
45 | # editer.buffer()
46 |
47 |
48 | print('正在下载插图.....................................')
49 | editer.get_image(is_gui=is_gui, signal=progressring_signal)
50 |
51 | print('正在编辑元数据....')
52 | editer.get_cover(is_gui=is_gui, signal=cover_signal)
53 | editer.get_toc()
54 | editer.get_content()
55 | editer.get_epub_head()
56 |
57 | print('正在生成电子书....')
58 | epub_file = editer.get_epub()
59 | print('生成成功!', f'电子书路径【{epub_file}】')
60 |
61 |
62 | def downloader_router(root_path,
63 | book_no,
64 | volume_no,
65 | interval=500,
66 | num_thread=1,
67 | is_gui=False,
68 | hang_signal=None,
69 | progressring_signal=None,
70 | cover_signal=None,
71 | edit_line_hang=None):
72 | is_multi_chap = False
73 | if len(book_no)==0:
74 | print('请检查输入是否完整正确!')
75 | return
76 | elif volume_no == '':
77 | query_chaps(book_no)
78 | return
79 | elif volume_no.isdigit():
80 | volume_no = int(volume_no)
81 | if volume_no<=0:
82 | print('请检查输入是否完整正确!')
83 | return
84 | elif "-" in volume_no:
85 | start, end = map(str, volume_no.split("-"))
86 | if start.isdigit() and end.isdigit() and int(start)>0 and int(start)
6 |
7 |
8 |
9 |
10 | """
11 | return text_html
12 |
13 | def get_cover_html(img_w, img_h):
14 | text_html = f"""
15 |
17 |
18 |
19 | Cover
20 |
21 |
22 |
23 |
26 |
27 |
28 | """
29 | return text_html
30 |
31 | def text2htmls(chap_name, text):
32 | text_html = f"""
33 |
35 |
36 |
37 | {chap_name}
38 |
39 |
40 |
41 | {chap_name}
42 | {text}
43 |
44 | """
45 | return text_html
46 |
47 | def get_toc_html(title, chap_names):
48 | toc_html_template = """
49 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | {title}
61 |
62 |
63 | {nav_points}
64 |
65 | """
66 | nav_point_template = """
67 |
68 | {chap_name}
69 |
70 |
71 | """
72 | nav_points = '\n'.join(
73 | nav_point_template.format(nav_id=i+1, play_order=i+1, chap_name=chap_name, chap_no=str(i).zfill(2))
74 | for i, chap_name in enumerate(chap_names)
75 | )
76 | return toc_html_template.format(title=title, nav_points=nav_points)
77 |
78 |
79 |
80 | def get_content_html(book_name, volume_name, volume_no, author, publisher, brief, tag_list, num_chap, num_img, img_exist=False):
81 | content_html_template = """
82 |
83 |
84 | zh-CN
85 |
86 |
87 | {title}
88 | {author}
89 | {publisher}
90 | {brief}
91 | {subjects}
92 |
93 |
94 |
95 |
96 |
97 | {xcolor}
98 | {chapters}
99 | {images}
100 |
101 |
102 |
103 | {spine_xcolor}
104 | {spine_chapters}
105 |
106 |
107 |
108 |
109 | """
110 |
111 | subjects = '\n'.join(f' {tag}' for tag in tag_list)
112 | chapters = '\n'.join(
113 | f' '
114 | for chap_no in range(num_chap)
115 | )
116 | images = '\n'.join(
117 | f' '
118 | for img_no in range(num_img)
119 | )
120 | spine_chapters = '\n'.join(
121 | f' '
122 | for chap_no in range(num_chap)
123 | )
124 |
125 | xcolor = ' \n' if img_exist else ''
126 | spine_xcolor = ' \n' if img_exist else ''
127 |
128 | return content_html_template.format(
129 | series_name=book_name,
130 | series_no = volume_no,
131 | title=book_name+'-'+volume_name,
132 | author=author,
133 | publisher=publisher,
134 | brief = brief,
135 | subjects=subjects,
136 | chapters=chapters,
137 | images=images,
138 | xcolor=xcolor,
139 | spine_xcolor=spine_xcolor,
140 | spine_chapters=spine_chapters
141 | )
142 |
143 | def check_chars(win_chars):
144 | win_illegal_chars = '?*"<>|:/'
145 | new_chars = ''
146 | for char in win_chars:
147 | if char in win_illegal_chars:
148 | new_chars += '\u25A0'
149 | else:
150 | new_chars += char
151 | return new_chars
152 |
153 | # def replace_rubbish_text(content_html):
154 | # soup = BeautifulSoup(content_html, 'html.parser')
155 | # ps = soup.find_all('p')
156 | # if not ps:
157 | # return str(soup)
158 | # last_p = ps[-1]
159 | # text = last_p.get_text()
160 | # sb = []
161 | # for blank_char in text:
162 | # # if blank_char in blank_list:
163 | # # continue
164 | # replacement = rubbish_secret_map.get(blank_char)
165 | # t = replacement if replacement else blank_char
166 | # sb.append(t)
167 | # last_p.string = ''.join(sb)
168 | # return str(soup)
169 |
170 | chinese_punctuation = ",。!?、;:“”‘’()《》〈〉【】『』〖〗…—~+-=×÷·—‘’“”『』【】()《》〈〉「」『』〖〗〘〙〚〛〚〛〘〙〖〗〘〙〚〛〘〙〖〗〘〙"
171 |
172 | def replace_rubbish_text(content_html):
173 | soup = BeautifulSoup(content_html, 'html.parser')
174 | ps = soup.find_all('p')
175 | if not ps:
176 | return str(soup)
177 | last_p = ps[-1]
178 | text = last_p.get_text()
179 | sb = []
180 | for blank_char in text:
181 | replace_strr = rubbish_secret_map.get(blank_char)
182 | if replace_strr is not None:
183 | sb.append(replace_strr)
184 | elif blank_char in chinese_punctuation:
185 | sb.append(blank_char)
186 | last_p.string = ''.join(sb)
187 | return str(soup)
--------------------------------------------------------------------------------
/bilinovel.py:
--------------------------------------------------------------------------------
1 | import argparse
2 | import os
3 |
4 | from backend.bilinovel.bilinovel_router import downloader_router
5 |
6 | def parse_args():
7 | """Parse input arguments."""
8 | parser = argparse.ArgumentParser(description='config')
9 | parser.add_argument('--interval', default=4500, type=int)
10 | parser.add_argument('--num_thread', default='1', type=int)
11 | parser.add_argument('--out_path', default='./out', type=str)
12 | args = parser.parse_args()
13 | return args
14 |
15 |
16 | if __name__=='__main__':
17 | args = parse_args()
18 | os.makedirs(args.out_path, exist_ok=True)
19 | while True:
20 | book_no = input('请输入书籍号:')
21 | volume_no = input('请输入卷号(查看目录信息不输入直接按回车,下载多卷请使用逗号分隔或者连字符-):')
22 | downloader_router(root_path=args.out_path, book_no=book_no, volume_no=volume_no, interval=args.interval, num_thread=args.num_thread)
23 | # book_no = '2650'
24 | # volume_no = '1'
25 | # downloader_router(root_path=args.out_path, book_no=book_no, volume_no=volume_no, interval=args.interval, num_thread=args.num_thread)
26 | # exit(0)
--------------------------------------------------------------------------------
/frontend/bilinovel_gui.py:
--------------------------------------------------------------------------------
1 | # coding:utf-8
2 | from PyQt5.QtCore import Qt, pyqtSignal, QThread, QRegExp
3 | from PyQt5.QtGui import QTextCursor, QPixmap, QRegExpValidator
4 | from PyQt5.QtWidgets import QFrame, QGridLayout
5 | from qfluentwidgets import TextEdit, ImageLabel, LineEdit, PushButton, ProgressRing, SubtitleLabel, EditableComboBox
6 |
7 | import sys
8 | import base64
9 | from resource.book import book_base64
10 | from frontend.cfg_utils import *
11 | from backend.bilinovel.bilinovel_router import downloader_router
12 | from .gui_utils import font_label, font_msg, EmittingStr
13 |
14 |
15 |
16 | class MainThread(QThread):
17 | def __init__(self, parent):
18 | super().__init__()
19 | self.parent = parent
20 | sys.stdout = self.parent.msg_out
21 | sys.stderr = self.parent.msg_out
22 |
23 | def run(self):
24 | self.parent.clear_signal.emit('')
25 | try:
26 | book_no = self.parent.editline_book.text()
27 | volumn_no = self.parent.editline_volumn.text()
28 | out_path = read_config_dict('download_path')
29 | interval = read_config_dict('interval')
30 | num_thread = read_config_dict('numthread')
31 | downloader_router(out_path, book_no, volumn_no, interval, num_thread, True, self.parent.hang_signal, self.parent.progressring_signal, self.parent.cover_signal, self.parent.editline_hang)
32 | self.parent.end_signal.emit('')
33 | except Exception as e:
34 | self.parent.end_signal.emit('')
35 | print('错误,请检查网络情况或确认输入是否正确')
36 | print('错误信息:')
37 | print(e)
38 | def terminate(self) -> None:
39 | result = super().terminate()
40 | return result
41 |
42 |
43 |
44 | class NovelWidget(QFrame):
45 |
46 | progressring_signal = pyqtSignal(object)
47 | end_signal = pyqtSignal(object)
48 | hang_signal = pyqtSignal(object)
49 | clear_signal = pyqtSignal(object)
50 | cover_signal = pyqtSignal(object)
51 |
52 | def __init__(self, text: str, parent=None):
53 | super().__init__(parent=parent)
54 | self.setObjectName(text)
55 | self.parent = parent
56 | self.label_book = SubtitleLabel('书号:', self)
57 | self.label_volumn = SubtitleLabel('卷号:', self)
58 | self.editline_book = LineEdit(self)
59 | self.editline_volumn = LineEdit(self)
60 | validator = QRegExpValidator(QRegExp("\\d+")) # 正则表达式匹配阿拉伯数字
61 | self.editline_book.setValidator(validator)
62 | # self.editline_volumn.setValidator(validator)
63 |
64 | self.editline_book.setMaxLength(4)
65 | # self.editline_volumn.setMaxLength(2)
66 |
67 | # self.editline_book.setText('2059')
68 | # self.editline_volumn.setText('3')
69 | self.book_icon = QPixmap()
70 | self.book_icon.loadFromData(base64.b64decode(book_base64))
71 | self.cover_w, self.cover_h = 152, 230
72 |
73 | self.label_cover = ImageLabel(self.book_icon, self)
74 | self.label_cover.setBorderRadius(8, 8, 8, 8)
75 | self.label_cover.setFixedSize(self.cover_w, self.cover_h)
76 |
77 | self.text_screen = TextEdit()
78 | self.text_screen.setReadOnly(True)
79 | self.text_screen.setFixedHeight(self.cover_h)
80 |
81 | self.progressRing = ProgressRing(self)
82 | self.progressRing.setValue(0)
83 | self.progressRing.setTextVisible(True)
84 | self.progressRing.setFixedSize(50, 50)
85 |
86 | self.btn_run = PushButton('确定', self)
87 | self.btn_run.setShortcut(Qt.Key_Return)
88 | self.btn_stop = PushButton('取消', self)
89 | self.btn_hang = PushButton('确定', self)
90 |
91 | self.editline_hang = EditableComboBox(self)
92 | self.gridLayout = QGridLayout(self)
93 | self.screen_layout = QGridLayout()
94 | self.btn_layout = QGridLayout()
95 | self.hang_layout = QGridLayout()
96 |
97 | self.label_book.setFont(font_label)
98 | self.label_volumn.setFont(font_label)
99 | self.editline_book.setFont(font_label)
100 | self.editline_volumn.setFont(font_label)
101 | self.text_screen.setFont(font_msg)
102 | self.editline_hang.setFont(font_msg)
103 |
104 | self.gridLayout.addWidget(self.editline_book, 0, 1)
105 | self.gridLayout.addWidget(self.editline_volumn, 1, 1)
106 | self.gridLayout.addWidget(self.label_book, 0, 0)
107 | self.gridLayout.addWidget(self.label_volumn, 1, 0)
108 |
109 | self.gridLayout.addLayout(self.btn_layout, 2, 1, 1, 1)
110 | self.btn_layout.addWidget(self.btn_run, 2, 1)
111 | self.btn_layout.addWidget(self.btn_stop, 2, 2)
112 |
113 | self.gridLayout.addLayout(self.screen_layout, 3, 0, 2, 2)
114 |
115 | self.screen_layout.addWidget(self.progressRing, 0, 1, Qt.AlignLeft|Qt.AlignBottom)
116 | self.screen_layout.addWidget(self.text_screen, 0, 0)
117 | self.screen_layout.addWidget(self.label_cover, 0, 1)
118 |
119 |
120 |
121 | self.gridLayout.addLayout(self.hang_layout, 5, 0, 1, 2)
122 | self.hang_layout.addWidget(self.editline_hang, 0, 0)
123 | self.hang_layout.addWidget(self.btn_hang, 0, 1)
124 |
125 | self.screen_layout.setContentsMargins(0,0,0,0)
126 | self.btn_layout.setContentsMargins(0,0,0,0)
127 | self.gridLayout.setContentsMargins(20, 10, 20, 10)
128 |
129 | self.btn_run.clicked.connect(self.process_start)
130 | self.btn_stop.clicked.connect(self.process_stop)
131 | self.btn_hang.clicked.connect(self.process_continue)
132 |
133 | self.progressring_signal.connect(self.progressring_msg)
134 | self.end_signal.connect(self.process_end)
135 | self.hang_signal.connect(self.process_hang)
136 | self.clear_signal.connect(self.clear_screen)
137 | self.cover_signal.connect(self.display_cover)
138 |
139 | self.progressRing.hide()
140 | self.btn_hang.hide()
141 | self.editline_hang.hide()
142 | self.btn_stop.setEnabled(False)
143 |
144 | self.msg_out = EmittingStr(textWritten=self.outputWritten)
145 |
146 | self.text_screen.setText(self.parent.novel_text)
147 |
148 | def process_start(self):
149 | self.label_cover.setImage(self.book_icon)
150 | self.label_cover.setFixedSize(self.cover_w, self.cover_h)
151 | self.btn_run.setEnabled(False)
152 | self.btn_run.setText('正在下载')
153 | self.btn_stop.setEnabled(True)
154 | self.main_thread = MainThread(self)
155 | self.main_thread.start()
156 |
157 | def process_end(self, input=None):
158 | self.btn_run.setEnabled(True)
159 | self.btn_run.setText('开始下载')
160 | self.btn_run.setShortcut(Qt.Key_Return)
161 | self.btn_stop.setEnabled(False)
162 | self.progressRing.hide()
163 | self.btn_hang.hide()
164 | self.editline_hang.clear()
165 | self.editline_hang.hide()
166 | if input=='refresh':
167 | self.label_cover.setImage(self.book_icon)
168 | self.label_cover.setFixedSize(self.cover_w, self.cover_h)
169 | self.clear_signal.emit('')
170 | self.text_screen.setText(self.parent.novel_text)
171 |
172 | def outputWritten(self, text):
173 | cursor = self.text_screen.textCursor()
174 | scrollbar=self.text_screen.verticalScrollBar()
175 | is_bottom = (scrollbar.value()>=scrollbar.maximum() - 15)
176 | cursor.movePosition(QTextCursor.End)
177 | cursor.insertText(text)
178 | if is_bottom:
179 | self.text_screen.setTextCursor(cursor)
180 | # self.text_screen.ensureCursorVisible()
181 |
182 | def clear_screen(self):
183 | self.text_screen.clear()
184 |
185 | def display_cover(self, signal_msg):
186 | filepath, img_h, img_w = signal_msg
187 | self.label_cover.setImage(filepath)
188 | self.label_cover.setFixedSize(int(img_w*self.cover_h/img_h), self.cover_h)
189 |
190 | def progressring_msg(self, input):
191 | if input == 'start':
192 | self.label_cover.setImage(self.book_icon)
193 | self.label_cover.setFixedSize(self.cover_w, self.cover_h)
194 | self.progressRing.show()
195 | elif input == 'end':
196 | self.progressRing.hide()
197 | self.progressRing.setValue(0)
198 | else:
199 | self.progressRing.setValue(input)
200 |
201 | def process_hang(self, input=None):
202 | self.btn_hang.setEnabled(True)
203 | self.btn_hang.setShortcut(Qt.Key_Return)
204 | self.btn_hang.show()
205 | self.editline_hang.show()
206 |
207 | def process_continue(self, input=None):
208 | self.btn_hang.hide()
209 | self.btn_hang.setEnabled(False)
210 | self.editline_hang.hide()
211 |
212 |
213 | def process_stop(self):
214 | self.main_thread.terminate()
215 | self.end_signal.emit('refresh')
--------------------------------------------------------------------------------
/frontend/cfg_utils.py:
--------------------------------------------------------------------------------
1 | import sqlite3
2 | import os
3 |
4 | DBPATH = './bilinovel-config.db'
5 |
6 | CREATE_CONFIG_TABLE_SQL = '''
7 | CREATE TABLE IF NOT EXISTS config (
8 | KEY TEXT PRIMARY KEY,
9 | VALUE TEXT
10 | );
11 | '''
12 |
13 | initial_config = {"download_path": './', "theme": "Auto", "interval": "4500", "numthread": '4'}
14 |
15 | def initialize_db():
16 | if not os.path.exists(DBPATH):
17 | with sqlite3.connect(DBPATH) as conn:
18 | cursor = conn.cursor()
19 | cursor.execute("PRAGMA journal_mode=DELETE")
20 | cursor.execute('''
21 | CREATE TABLE IF NOT EXISTS config (
22 | KEY TEXT PRIMARY KEY,
23 | VALUE TEXT
24 | );
25 | ''')
26 | for key, value in initial_config.items():
27 | cursor.execute("INSERT OR REPLACE INTO config (KEY, VALUE) VALUES (?, ?)", (key, value))
28 | conn.commit()
29 |
30 | def read_config_dict(key=None):
31 | with sqlite3.connect(DBPATH) as conn:
32 | cursor = conn.cursor()
33 | if key is None:
34 | return None
35 | else:
36 | cursor.execute("SELECT VALUE FROM config WHERE KEY = ?", (key,))
37 | result = cursor.fetchone()
38 | return result[0] if result else None
39 |
40 | def write_config_dict(key, value):
41 | with sqlite3.connect(DBPATH) as conn:
42 | cursor = conn.cursor()
43 | cursor.execute("PRAGMA journal_mode=DELETE")
44 | cursor.execute("INSERT OR REPLACE INTO config (KEY, VALUE) VALUES (?, ?)", (key, value))
45 | conn.commit()
--------------------------------------------------------------------------------
/frontend/gui_utils.py:
--------------------------------------------------------------------------------
1 | # coding:utf-8
2 | from PyQt5.QtCore import pyqtSignal, QObject
3 | from PyQt5.QtGui import QFont
4 |
5 | font_label = QFont('幼圆', 18)
6 | font_label.setBold(True)
7 | font_msg = QFont('幼圆', 12)
8 | font_msg.setBold(True)
9 |
10 | class EmittingStr(QObject):
11 | textWritten = pyqtSignal(str) # 定义一个发送str的信号
12 | def write(self, text):
13 | self.textWritten.emit(str(text))
14 | def flush(self):
15 | pass
16 | def isatty(self):
17 | pass
18 | # sys.stdout = EmittingStr(textWritten=outputWritten)
19 | # sys.stderr = EmittingStr(textWritten=outputWritten)
--------------------------------------------------------------------------------
/frontend/mainwindow.py:
--------------------------------------------------------------------------------
1 | # coding:utf-8
2 | from PyQt5.QtCore import QTimer, QSize
3 | from PyQt5.QtGui import QIcon, QPixmap, QColor
4 | from PyQt5.QtWidgets import QApplication
5 | from qfluentwidgets import (setTheme, Theme, Theme, setTheme, Theme, FluentWindow, NavigationItemPosition, qconfig, SplashScreen)
6 |
7 | from qfluentwidgets import FluentIcon as FIF
8 | import base64
9 | from resource.logo import logo_base64
10 | from resource.logo_big import logo_big_base64
11 | from backend.bilinovel.bilinovel_router import *
12 |
13 | from frontend.cfg_utils import *
14 | from frontend.gui_utils import font_label
15 | from frontend.bilinovel_gui import NovelWidget
16 | from frontend.setting import SettingWidget
17 |
18 | class MainWindow(FluentWindow):
19 | def __init__(self):
20 | super().__init__()
21 | pixmap = QPixmap()
22 | pixmap.loadFromData(base64.b64decode(logo_big_base64))
23 | # create splash screen
24 | self.splashScreen = SplashScreen(QIcon(pixmap), self)
25 | self.splashScreen.setIconSize(QSize(400, 400))
26 | self.splashScreen.raise_()
27 |
28 | initialize_db()
29 | self.out_path = read_config_dict("download_path")
30 | split_str = '--------------------------------\n '
31 | self.novel_text = f'使用说明(必看):\n{split_str}1. https://www.linovelib.com,输入书号以及下载的卷号,例如网址是https://www.linovelib.com/novel/2704.html,则书号输入2704。若不确定卷号,可以只输入书号,点击确定会返回书籍卷名称和对应的卷号。\n{split_str}3.要下载编号[2]对应卷,则卷号输入2。想下载多卷比如[1]至[3]对应卷,则卷号输入1-3或1,2,3(英文逗号分隔,编号可以不连续)。'
32 | self.interval = read_config_dict('interval')
33 | self.NovelInterface = NovelWidget('Novel Interface', self)
34 | self.settingInterface = SettingWidget('Setting Interface', self)
35 | self.initNavigation()
36 | self.initWindow()
37 | QTimer.singleShot(50, lambda: self.set_theme(read_config_dict('theme')))
38 | QTimer.singleShot(2000, lambda: self.splashScreen.close())
39 |
40 | def initNavigation(self):
41 | self.addSubInterface(self.NovelInterface, FIF.BOOK_SHELF, '哔哩轻小说')
42 | self.addSubInterface(self.settingInterface, FIF.SETTING, '设置', NavigationItemPosition.BOTTOM)
43 |
44 | def initWindow(self):
45 | self.resize(700, 460)
46 | pixmap = QPixmap()
47 | pixmap.loadFromData(base64.b64decode(logo_base64))
48 |
49 | self.setWindowIcon(QIcon(pixmap))
50 | self.setWindowTitle('哔哩轻小说EPUB下载器-Edge浏览器版')
51 | self.setFont(font_label)
52 |
53 | desktop = QApplication.desktop().availableGeometry()
54 | w, h = desktop.width(), desktop.height()
55 | self.move(w//2 - self.width()//2, h//2 - self.height()//2)
56 |
57 | def set_theme(self, mode=None):
58 | if mode=='Light':
59 | setTheme(Theme.LIGHT)
60 | elif mode=='Dark':
61 | setTheme(Theme.DARK)
62 | elif mode== 'Auto':
63 | setTheme(Theme.AUTO)
64 | theme = qconfig.theme
65 | if theme == Theme.DARK:
66 | self.NovelInterface.label_book.setTextColor(QColor(255,255,255))
67 | self.NovelInterface.label_volumn.setTextColor(QColor(255,255,255))
68 | elif theme == Theme.LIGHT:
69 | self.NovelInterface.label_book.setTextColor(QColor(0,0,0))
70 | self.NovelInterface.label_volumn.setTextColor(QColor(0,0,0))
--------------------------------------------------------------------------------
/frontend/setting.py:
--------------------------------------------------------------------------------
1 | # coding:utf-8
2 | from PyQt5.QtCore import Qt
3 | from PyQt5.QtWidgets import QFileDialog, QWidget
4 | from qfluentwidgets import (Theme, PushSettingCard, SettingCardGroup, ExpandLayout, Theme, Theme, OptionsSettingCard, OptionsConfigItem, OptionsValidator, RangeSettingCard, ScrollArea, RangeValidator, RangeConfigItem)
5 |
6 | from qfluentwidgets import FluentIcon as FIF
7 | from .cfg_utils import read_config_dict, write_config_dict
8 | import os
9 | import shutil
10 |
11 |
12 | class SettingWidget(ScrollArea):
13 | def __init__(self, text, parent=None):
14 | super().__init__(parent=parent)
15 | self.scrollWidget = QWidget()
16 | self.expandLayout = ExpandLayout(self.scrollWidget)
17 | self.parent = parent
18 | self.setting_group = SettingCardGroup(self.tr("设置"), self.scrollWidget)
19 |
20 | self.download_path_card = PushSettingCard(
21 | self.tr('选择文件夹'),
22 | FIF.DOWNLOAD,
23 | self.tr("下载目录"),
24 | self.parent.out_path,
25 | self.setting_group
26 | )
27 |
28 | theme_name = read_config_dict('theme')
29 | if theme_name == 'Light':
30 | self.themeMode = OptionsConfigItem(
31 | None, "ThemeMode", Theme.LIGHT, OptionsValidator(Theme), None)
32 | elif theme_name == 'Dark':
33 | self.themeMode = OptionsConfigItem(
34 | None, "ThemeMode", Theme.DARK, OptionsValidator(Theme), None)
35 | else:
36 | self.themeMode = OptionsConfigItem(
37 | None, "ThemeMode", Theme.AUTO, OptionsValidator(Theme), None)
38 |
39 | self.interval_card = RangeSettingCard(
40 | RangeConfigItem("interval", "时间间隔", int(read_config_dict("interval")), RangeValidator(0, 8000)),
41 | FIF.DATE_TIME,
42 | self.tr('下载时间间隔(毫秒)'),
43 | self.tr('如果页面频繁陷入超时,建议适当延长下载间隔'),
44 | self.setting_group
45 | )
46 |
47 | self.thread_card = RangeSettingCard(
48 | RangeConfigItem("thread", "下载线程数量", int(read_config_dict("numthread")), RangeValidator(1, 10)),
49 | FIF.SPEED_HIGH,
50 | self.tr('小说插画下载线程数量'),
51 | self.tr('适当增加充分利用带宽,但不要太高'),
52 | self.setting_group
53 | )
54 |
55 | self.theme_card = OptionsSettingCard(
56 | self.themeMode,
57 | FIF.BRUSH,
58 | self.tr('应用主题'),
59 | self.tr("更改外观"),
60 | texts=[
61 | self.tr('亮'), self.tr('暗'),
62 | self.tr('跟随系统')
63 | ],
64 | parent=self.setting_group
65 | )
66 |
67 |
68 | self.resize(1000, 800)
69 | self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
70 | self.setViewportMargins(0, 10, 0, 20)
71 | self.setWidget(self.scrollWidget)
72 | self.setWidgetResizable(True)
73 | self.setObjectName('settingInterface2')
74 |
75 | # initialize style sheet
76 | self.scrollWidget.setObjectName('scrollWidget')
77 | qss = '''
78 | SettingInterface, #scrollWidget {
79 | background-color: transparent;
80 | }
81 |
82 | QScrollArea {
83 | border: none;
84 | background-color: transparent;
85 | }
86 |
87 | QLabel#settingLabel {
88 | font: 33px 'Microsoft YaHei Light';
89 | background-color: transparent;
90 | color: white;
91 | }
92 |
93 | '''
94 |
95 | self.setStyleSheet(qss)
96 |
97 | self.setting_group.addSettingCard(self.download_path_card)
98 | self.setting_group.addSettingCard(self.interval_card)
99 | self.setting_group.addSettingCard(self.thread_card)
100 | self.setting_group.addSettingCard(self.theme_card)
101 | self.expandLayout.setSpacing(28)
102 | self.expandLayout.setContentsMargins(20, 10, 20, 0)
103 | self.expandLayout.addWidget(self.setting_group)
104 |
105 | self.download_path_card.clicked.connect(self.download_path_changed)
106 | self.theme_card.optionChanged.connect(self.theme_changed)
107 | self.interval_card.valueChanged.connect(self.interval_changed)
108 | self.thread_card.valueChanged.connect(self.thread_changed)
109 |
110 |
111 | def download_path_changed(self):
112 | """ download folder card clicked slot """
113 | new_path = QFileDialog.getExistingDirectory(
114 | self, self.tr("Choose folder"), self.parent.out_path)
115 | if new_path:
116 | write_config_dict("download_path", new_path)
117 | self.download_path_card.contentLabel.setText(read_config_dict("download_path"))
118 |
119 | def theme_changed(self):
120 | theme_name = self.theme_card.choiceLabel.text()
121 | if theme_name == '亮':
122 | theme_mode = 'Light'
123 | elif theme_name == '暗':
124 | theme_mode = 'Dark'
125 | elif theme_name == '跟随系统':
126 | theme_mode = 'Auto'
127 | write_config_dict("theme", theme_mode)
128 | self.parent.set_theme(read_config_dict("theme"))
129 | self.delete()
130 |
131 | def interval_changed(self):
132 | interval = self.interval_card.valueLabel.text()
133 | write_config_dict("interval", interval)
134 | self.delete()
135 |
136 | def thread_changed(self):
137 | num_thread = self.thread_card.valueLabel.text()
138 | write_config_dict("numthread", num_thread)
139 | self.delete()
140 |
141 | def delete(self):
142 | if os.path.exists('./config'):
143 | shutil.rmtree('./config')
--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------
1 | # coding:utf-8
2 | from PyQt5.QtCore import Qt
3 | from PyQt5.QtWidgets import QApplication
4 | from qfluentwidgets import (setTheme, Theme, setThemeColor)
5 | import sys
6 | from frontend.mainwindow import MainWindow
7 |
8 | if __name__ == '__main__':
9 | QApplication.setHighDpiScaleFactorRoundingPolicy(Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)
10 | QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
11 | QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)
12 |
13 | setTheme(Theme.DARK)
14 | setThemeColor('#FF7233')
15 | app = QApplication(sys.argv)
16 | w = MainWindow()
17 | w.show()
18 | app.exec_()
19 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | # pip install -r requirements.txt -i https://pypi.org/simple/
2 | requests
3 | bs4
4 | rich
5 | pyqt5
6 | PyQt-Fluent-Widgets[full]
7 | selenium
8 | DrissionPage
--------------------------------------------------------------------------------
/resource/__pycache__/book.cpython-311.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShqWW/bilinovel-download/5e3632da9f7536f5d506c24a3f21ab956f6d7db6/resource/__pycache__/book.cpython-311.pyc
--------------------------------------------------------------------------------
/resource/__pycache__/logo.cpython-311.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShqWW/bilinovel-download/5e3632da9f7536f5d506c24a3f21ab956f6d7db6/resource/__pycache__/logo.cpython-311.pyc
--------------------------------------------------------------------------------
/resource/__pycache__/logo_big.cpython-311.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShqWW/bilinovel-download/5e3632da9f7536f5d506c24a3f21ab956f6d7db6/resource/__pycache__/logo_big.cpython-311.pyc
--------------------------------------------------------------------------------
/resource/book.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShqWW/bilinovel-download/5e3632da9f7536f5d506c24a3f21ab956f6d7db6/resource/book.png
--------------------------------------------------------------------------------
/resource/book.py:
--------------------------------------------------------------------------------
1 | book_base64 = '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'
--------------------------------------------------------------------------------
/resource/example1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShqWW/bilinovel-download/5e3632da9f7536f5d506c24a3f21ab956f6d7db6/resource/example1.png
--------------------------------------------------------------------------------
/resource/example2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShqWW/bilinovel-download/5e3632da9f7536f5d506c24a3f21ab956f6d7db6/resource/example2.png
--------------------------------------------------------------------------------
/resource/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShqWW/bilinovel-download/5e3632da9f7536f5d506c24a3f21ab956f6d7db6/resource/logo.png
--------------------------------------------------------------------------------
/resource/logo.py:
--------------------------------------------------------------------------------
1 | logo_base64 = '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'
--------------------------------------------------------------------------------
/resource/logo_big.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShqWW/bilinovel-download/5e3632da9f7536f5d506c24a3f21ab956f6d7db6/resource/logo_big.png
--------------------------------------------------------------------------------
/resource/logo_big.py:
--------------------------------------------------------------------------------
1 | logo_big_base64 = 'iVBORw0KGgoAAAANSUhEUgAAAeMAAABkCAMAAACCa6jeAAAA21BMVEVHcEz////5lW3////4/P7/////////////////////////////////////////v7/////////////////////////////9k276lWv////8lm77lm77lW3////////7lW78lm//////////UxAAnuL7lm7/Uw//VBD/t6X/3dXU6fj/imqj0vFnuenl8vv/r5XC4fX/5d3+m3r/7un/XST/18v+yrckpeT/wa3/zr8/rOaHxu3/+PX/c0V4wOv/fVb9qIdUs+j/aTf/9PCw2PP/ooeWzO/+tpz/km8gTz35AAAAInRSTlMAdfDE/kWIgcBBM7Ce3BIECPLSIWiRUxUz5cOZgGEn3Fu6k74QPAAAGe5JREFUeNrtnQlbGknXhkU2FQIu6MQl0RRJN/sOgoK46///Rd85Vd08XVSvJt/kdS6eyUwSBGX67rPWqWJro4022mijjTbaaKONNvq3VP56dXF+drm9/Wt7+/Ls7Pzi6mt5a6P/EN+Ls+1fhrbPLjac/xv6eg6+Jufzr1sbfW6VLy5/Rejy4p+tjT6t/tFMOMSYN5Q/K+ELEI6ivLHlT6krEI5D+Wpro8+l8j9nBsb76X0Y5bONKX9yIx5MhkKIeW1jyv8Rlc/X8HX7Qmn+K0znm3L57+toL86z1vz0YDoUrOGUSN//2vjr/xHtpQopg2fqVIiDYmU/qulx6WfCs6ZtT4R4iyiW/0BLJJtLC9Lh8cnWXxa/EVZ6Z2cnn8+f7O7+7ziqSkGwivo7Kgil09z3UMTeUHyvCA+7j1a1Wp2BcZC2E0BOFQ58YObFSse/47J2Sb8HhQib+rJTyOezpQ8b3+5JPrv/24S/F4WjonZFxUqZb+GIob4y4VHVqtrV6liI7q8/Bfnohy/MivAolzSR2N3NEoF9dllSsMPjPH1hdzcBnJwI0WElOZiT40PnPsn+JuJvGbFSxXMDCVKn40D+HhiL9d4lPbe2sKqWXbXAOFyXMWMyIYZgyYfCo4OtJNrHzS0gH0vMZ31fntUsrHQgQqVBTnhPf9n/PStWiHt3DXbLJc1TZ65/3rQk5m+hiKG5EI/VqiV/2WAcC/Ju+P+yIJmGXOLkvVklLdmH7CWJ419EbOX9Xq7HsRO2iZ9SbVKr1eo1Gl5LTgjm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'
--------------------------------------------------------------------------------
/resource/mainpage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShqWW/bilinovel-download/5e3632da9f7536f5d506c24a3f21ab956f6d7db6/resource/mainpage.png
--------------------------------------------------------------------------------
/resource/trans_base64.py:
--------------------------------------------------------------------------------
1 | from PIL import Image
2 | import base64
3 | # from resource.logo import logo_base64
4 | import io
5 |
6 | # # 从Base64编码数据中获取图像数据
7 | # image_bytes = base64.b64decode(logo_base64)
8 |
9 | # # 将图像数据解码为Image对象
10 | # image = Image.open(io.BytesIO(image_bytes))
11 |
12 | # # 显示图像
13 | # image.show()
14 |
15 |
16 | def image_to_base64(image_path):
17 | with open(image_path, "rb") as image_file:
18 | encoded_string = base64.b64encode(image_file.read())
19 | return encoded_string.decode("utf-8")
20 |
21 | image_path = "logo_big.png "
22 | base64_string = image_to_base64(image_path)
23 | print(base64_string)
--------------------------------------------------------------------------------