From 2309bfbc9942251f60d6e8785e7b389cf0ed19bf Mon Sep 17 00:00:00 2001 From: Zeal-L Date: Wed, 16 Aug 2023 01:46:53 +1000 Subject: [PATCH 1/8] =?UTF-8?q?:art:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81:=20=E5=8E=BB=E9=99=A4Episode=E7=B1=BB=E9=87=8C?= =?UTF-8?q?=E4=B8=8EmainGUI=E7=9B=B8=E5=85=B3=E7=9A=84=E5=87=BD=E6=95=B0?= =?UTF-8?q?=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/BiliPlus.py | 22 +++++++++---------- src/Episode.py | 52 ++++++++++++++++++++------------------------ src/ui/DownloadUI.py | 2 +- 3 files changed, 35 insertions(+), 41 deletions(-) diff --git a/src/BiliPlus.py b/src/BiliPlus.py index c40a1da..d73ffa7 100644 --- a/src/BiliPlus.py +++ b/src/BiliPlus.py @@ -53,14 +53,12 @@ def getEpisodesInfo(self) -> list[Episode]: if epi.isDownloaded(): self.num_downloaded += 1 - self.retrieveAvailableEpisode(self.episodes, self.comic_id, self.mainGUI) + self.retrieveAvailableEpisode(self.episodes, self.comic_id) return self.episodes ############################################################ - def retrieveAvailableEpisode( - self, episodes: list[BiliPlusEpisode], comic_id: str, mainGUI: MainGUI - ): + def retrieveAvailableEpisode(self, episodes: list[BiliPlusEpisode], comic_id: str): """从BiliPlus重新获取解锁状态""" biliplus_detail_url = ( f"https://www.biliplus.com/manga/?act=detail_preview&mangaid={comic_id}" @@ -81,7 +79,7 @@ def _(url: str = biliplus_detail_url) -> dict: logger.warning(f"漫画id:{self.comic_id} 在BiliPlus获取漫画信息失败! 重试中...\n{e}") raise e if "未登录" in res.text: - mainGUI.signal_message_box.emit("请先在设置界面填写正确的BiliPlus Cookie!") + self.mainGUI.signal_message_box.emit("请先在设置界面填写正确的BiliPlus Cookie!") return if res.status_code != 200: logger.warning( @@ -110,7 +108,9 @@ def _(url: str = biliplus_detail_url) -> dict: total_ep = total_ep_element.contents[0].split("/")[1] total_pages = int(int(total_ep) / 200) + 1 for pages in range(2, total_pages + 1): - mainGUI.signal_resolve_status.emit(f"正在解析漫画章节({pages}/{total_pages})...") + self.mainGUI.signal_resolve_status.emit( + f"正在解析漫画章节({pages}/{total_pages})..." + ) page_html = _(f"{biliplus_detail_url}&page={pages}") document = BeautifulSoup(page_html, "html.parser") ep_items = document.find_all("div", {"class": "episode-item"}) @@ -123,7 +123,7 @@ def _(url: str = biliplus_detail_url) -> dict: except Exception as e: logger.error(f"漫画id:{self.comic_id} 在处理BiliPlus解锁章节数据时失败!\n{e}") logger.exception(e) - mainGUI.signal_message_box.emit( + self.mainGUI.signal_message_box.emit( f"漫画id:{self.comic_id} 在处理BiliPlus解锁章节数据时失败!\n\n更多详细信息请查看日志文件, 或联系开发者!" ) @@ -146,7 +146,7 @@ def __init__( self.comic_id = comic_id ############################################################ - def init_imgsList(self, mainGUI: MainGUI) -> bool: + def init_imgsList(self) -> bool: """重写用于初始化从BiliPlus获取的章节内所有图片的列表(自带token) Returns @@ -186,7 +186,7 @@ def _() -> list[dict]: f"《{self.comic_name}》章节:{self.title} 从BiliPlus重复获取图片列表多次后失败!,跳过!\n{e}" ) logger.exception(e) - mainGUI.signal_message_box.emit( + self.mainGUI.signal_message_box.emit( f"《{self.comic_name}》章节:{self.title} 从BiliPlus重复获取图片列表多次后失败!\n已暂时跳过此章节!\n请检查网络连接或者重启软件!\n\n更多详细信息请查看日志文件, 或联系开发者!" ) return False @@ -206,7 +206,7 @@ def _() -> list[dict]: logger.error( f"《{self.comic_name}》章节:{self.title} 在处理BiliPlus地址时因Cookie有误导致失败!" ) - mainGUI.signal_message_box.emit( + self.mainGUI.signal_message_box.emit( f"《{self.comic_name}》章节:{self.title} 在处理BiliPlus解锁章节图片地址时因Cookie有误导致失败!" ) return False @@ -215,7 +215,7 @@ def _() -> list[dict]: f"《{self.comic_name}》章节:{self.title} 在处理BiliPlus解锁章节图片地址时失败!\n{e}" ) logger.exception(e) - mainGUI.signal_message_box.emit( + self.mainGUI.signal_message_box.emit( f"《{self.comic_name}》章节:{self.title} 在处理BiliPlus解锁章节图片地址时失败!\n\n更多详细信息请查看日志文件, 或联系开发者!" ) return False diff --git a/src/Episode.py b/src/Episode.py index a048ca0..7a56b7d 100644 --- a/src/Episode.py +++ b/src/Episode.py @@ -99,7 +99,7 @@ def __init__( self.epi_path = self.epi_path_7z ############################################################ - def init_imgsList(self, mainGUI: MainGUI) -> bool: + def init_imgsList(self) -> bool: """初始化章节内所有图片的列表和图片的token Returns @@ -137,7 +137,7 @@ def _() -> list[dict]: except requests.RequestException as e: logger.error(f"《{self.comic_name}》章节:{self.title} 重复获取图片列表多次后失败!,跳过!\n{e}") logger.exception(e) - mainGUI.signal_message_box.emit( + self.mainGUI.signal_message_box.emit( f"《{self.comic_name}》章节:{self.title} 重复获取图片列表多次后失败!\n已暂时跳过此章节!\n请检查网络连接或者重启软件!\n\n更多详细信息请查看日志文件, 或联系开发者!" ) return False @@ -176,7 +176,7 @@ def _() -> list[dict]: f"《{self.comic_name}》章节:{self.title} 重复获取图片token多次后失败,跳过!\n{e}" ) logger.exception(e) - mainGUI.signal_message_box.emit( + self.mainGUI.signal_message_box.emit( f"《{self.comic_name}》章节:{self.title} 重复获取图片token多次后失败!\n已暂时跳过此章节!\n请检查网络连接或者重启软件!\n\n更多详细信息请查看日志文件, 或联系开发者!" ) return False @@ -185,19 +185,18 @@ def _() -> list[dict]: ############################################################ def download( - self, mainGUI: MainGUI, signal_rate_progress: SignalInstance, taskID: str + self, signal_rate_progress: SignalInstance, taskID: str ) -> None: """下载章节内所有图片 并合并为PDF Args: - mainGUI (MainGUI): 主窗口类实例 rate_progress (SignalInstance): 信号槽,用于更新下载进度条 taskID (str): 任务ID """ # ?########################################################### # ? 初始化下载图片需要的参数 - if not self.init_imgsList(mainGUI): + if not self.init_imgsList(): signal_rate_progress.emit( { "taskID": taskID, @@ -211,7 +210,7 @@ def download( for index, img in enumerate(self.imgs_token, start=1): img_url = f"{img['url']}?token={img['token']}" - img_path = self.downloadImg(mainGUI, index, img_url) + img_path = self.downloadImg(index, img_url) if img_path is None: signal_rate_progress.emit( { @@ -219,7 +218,7 @@ def download( "rate": -1, } ) - self.clearAfterSave(mainGUI, imgs_path) + self.clearAfterSave(imgs_path) return imgs_path.append(img_path) @@ -235,20 +234,19 @@ def download( # ? 保存图片 if self.save_method == "PDF": - self.saveToPDF(mainGUI, imgs_path) + self.saveToPDF(imgs_path) elif self.save_method == "文件夹-图片": - self.saveToFolder(mainGUI, imgs_path) + self.saveToFolder(imgs_path) elif self.save_method == "7z压缩包": - self.saveTo7z(mainGUI, imgs_path) + self.saveTo7z(imgs_path) - self.clearAfterSave(mainGUI, imgs_path) + self.clearAfterSave(imgs_path) ############################################################ - def clearAfterSave(self, mainGUI: MainGUI, imgs_path: list[str]) -> None: + def clearAfterSave(self, imgs_path: list[str]) -> None: """删除临时图片, 偶尔会出现删除失败的情况,故给与重试5次 Args: - mainGUI (MainGUI): 主窗口类实例 imgs_path (list): 临时图片路径列表 """ @@ -273,16 +271,15 @@ def _() -> None: f"《{self.comic_name}》章节:{self.title} 删除临时图片多次后失败!\n{imgs_path}\n{e}" ) logger.exception(e) - mainGUI.signal_message_box.emit( + self.mainGUI.signal_message_box.emit( f"《{self.comic_name}》章节:{self.title} 删除临时图片多次后失败!\n请手动删除!\n\n更多详细信息请查看日志文件, 或联系开发者!" ) ############################################################ - def saveToPDF(self, mainGUI: MainGUI, imgs_path: list[str]) -> None: + def saveToPDF(self, imgs_path: list[str]) -> None: """将图片保存为PDF文件 Args: - mainGUI (MainGUI): 主窗口类实例 imgs_path (list): 临时图片路径列表 """ @@ -326,16 +323,15 @@ def _(): except OSError as e: logger.error(f"《{self.comic_name}》章节:{self.title} 合并PDF多次后失败!\n{e}") logger.exception(e) - mainGUI.signal_message_box.emit( + self.mainGUI.signal_message_box.emit( f"《{self.comic_name}》章节:{self.title} 合并PDF多次后失败!\n已暂时跳过此章节!\n请重新尝试或者重启软件!\n\n更多详细信息请查看日志文件, 或联系开发者!" ) ############################################################ - def saveToFolder(self, mainGUI: MainGUI, imgs_path: list[str]) -> None: + def saveToFolder(self, imgs_path: list[str]) -> None: """将图片保存到文件夹 Args: - mainGUI (MainGUI): 主窗口类实例 imgs_path (list): 临时图片路径列表 """ @@ -392,20 +388,19 @@ def jpg_exif(): except OSError as e: logger.error(f"《{self.comic_name}》章节:{self.title} 保存图片到文件夹多次后失败!\n{e}") logger.exception(e) - mainGUI.signal_message_box.emit( + self.mainGUI.signal_message_box.emit( f"《{self.comic_name}》章节:{self.title} 保存图片到文件夹多次后失败!\n已暂时跳过此章节!\n请重新尝试或者重启软件!\n\n更多详细信息请查看日志文件, 或联系开发者!" ) ############################################################ - def saveTo7z(self, mainGUI: MainGUI, imgs_path: list[str]) -> None: + def saveTo7z(self, imgs_path: list[str]) -> None: """将图片保存到7z压缩文件 Args: - mainGUI (MainGUI): 主窗口类实例 imgs_path (list): 临时图片路径列表 """ - self.saveToFolder(mainGUI, imgs_path) + self.saveToFolder(imgs_path) @retry(stop_max_attempt_number=5) def _(): @@ -430,16 +425,15 @@ def _(): except OSError as e: logger.error(f"《{self.comic_name}》章节:{self.title} 保存图片到7z多次后失败!\n{e}") logger.exception(e) - mainGUI.signal_message_box.emit( + self.mainGUI.signal_message_box.emit( f"《{self.comic_name}》章节:{self.title} 保存图片到7z多次后失败!\n已暂时跳过此章节!\n请重新尝试或者重启软件!\n\n更多详细信息请查看日志文件, 或联系开发者!" ) ############################################################ - def downloadImg(self, mainGUI: MainGUI, index: int, img_url: str) -> str: + def downloadImg(self, index: int, img_url: str) -> str: """根据 url 和 token 下载图片 Args: - mainGUI (MainGUI): 主窗口类实例 index (int): 章节中图片的序号 img_url (str): 图片的合法 url @@ -480,7 +474,7 @@ def _() -> bytes: f"《{self.comic_name}》章节:{self.title} - {index} - {img_url} 重复下载图片多次后失败!\n{e}" ) logger.exception(e) - mainGUI.signal_message_box.emit( + self.mainGUI.signal_message_box.emit( f"《{self.comic_name}》章节:{self.title} 重复下载图片多次后失败!\n已暂时跳过此章节!\n请检查网络连接或者重启软件!\n\n更多详细信息请查看日志文件, 或联系开发者!" ) return None @@ -508,7 +502,7 @@ def _() -> None: f"《{self.comic_name}》章节:{self.title} - {index} - {img_url} - {path_to_save} - 保存图片多次后失败!\n{e}" ) logger.exception(e) - mainGUI.signal_message_box.emit( + self.mainGUI.signal_message_box.emit( f"《{self.comic_name}》章节:{self.title} - {index} - 保存图片多次后失败!\n已暂时跳过此章节, 并删除所有缓存文件!\n请重新尝试或者重启软件!\n\n更多详细信息请查看日志文件, 或联系开发者!" ) return None diff --git a/src/ui/DownloadUI.py b/src/ui/DownloadUI.py index 670f9cf..33ba326 100644 --- a/src/ui/DownloadUI.py +++ b/src/ui/DownloadUI.py @@ -138,7 +138,7 @@ def addTask(self, mainGUI: MainGUI, epi: Episode) -> None: self.all_tasks[task_id] = { "rate": 0, "future": self.executor.submit( - epi.download, mainGUI, self.signal_rate_progress, task_id + epi.download, self.signal_rate_progress, task_id ), } From 1858d7f7108918d9ef49aac82c851fdf35b61f53 Mon Sep 17 00:00:00 2001 From: Zeal-L Date: Wed, 16 Aug 2023 02:28:11 +1000 Subject: [PATCH 2/8] =?UTF-8?q?:memo:=20=E4=BC=98=E5=8C=96=20README.md=20?= =?UTF-8?q?=E9=87=8C=E7=9A=84=E6=9B=B4=E6=96=B0=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 48 +--------------------------------------------- UPDATE.md | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/Episode.py | 1 + 3 files changed, 54 insertions(+), 47 deletions(-) create mode 100644 UPDATE.md diff --git a/README.md b/README.md index e281363..5d5a181 100644 --- a/README.md +++ b/README.md @@ -139,53 +139,7 @@ ![Alt](https://repobeats.axiom.co/api/embed/cc62fded834eb06fc9b30cf7ffd54eeb53d700fc.svg "Repobeats analytics image") -### v1.3.0 - *2022-08-11* -- 新增功能: - - 二维码登入 - - 利用 [biliplus](https://www.biliplus.com/) 提供的 [ComicWebReader](https://www.biliplus.com/manga/) 在线漫画平台的api来尝试获取未解锁的漫画章节 -- 优化配置: - - 移除保存文件夹名里的漫画ID信息;元数据现在默认保存,并且以此来初始化我的库存 - - 老用户需要重新下载一章漫画,然后把以前下载好的移动到新文件夹中 -- 修复bug: - - 修复个别`png`保存为`jpg`的情况 - - 修复`BiliPlus Cookie`检测可能出现的隐藏`bug` ([#61][i61]) - - 修复`BiliPlus`可以看未解锁的漫画章节,软件无法下载 ([#52][i52]) - -[i52]: https://github.com/Zeal-L/BiliBili-Manga-Downloader/issues/52 -[i61]: https://github.com/Zeal-L/BiliBili-Manga-Downloader/issues/61 - -### v1.2.0 - *2022-06-20* -- 新增功能: 现在可以一键保存漫画的元数据了,包括漫画封面,漫画信息, 等等 (json格式) ([#39][i39]) -- 重大优化: 对于 `文件夹-图片形式` 和 `7z压缩包` 的保存方式取消了对漫画原图像的二次压缩,现在图像保存的质量和原图一致。~~(虽然用肉眼看不出来)~~ 由于 `PDF` 保存格式的特殊性,仍然会进行二次压缩和信道转换 - -[i39]: https://github.com/Zeal-L/BiliBili-Manga-Downloader/issues/39 - - -### v1.1.0 - *2022-05-19* -- 新增功能: 添加了多种主题选择 -- 新增功能: 一键检查软件更新 -- 修复bug: 修复了一个可能会导致启动失败的保存路径设置;现在如果保存的路径意外失效会初始化为默认路径(cwd) - -### v1.0.4 - *2022-04-24* -- 优化使用体验: 我的库存列表现在按照漫画名排序 -- 优化项目结构: 重新分类了原始UI文件和资源文件,并更新了打包脚本 -- 更新依赖项:更新了过去一个月积攒的 pyside6,pillow 等 python 库的新版本 - -### v1.0.3 - *2022-03-12* -- 修复bug: 使用7z保存时可能会因为文件名特殊字符引起报错;添加针对“.”的正则过滤 -- 优化配置: 全局网络请求的 timeout 以及 max retry 值增大一倍 - -### v1.0.2 - *2022-03-11* -- 修复bug: 总进度条数值错误更新为0 -- 优化设置选项: 以防在网络不好的情况下高线程数导致的频繁重试警告,最大线程数现在设为32 - -### v1.0.1 - *2022-03-9* -- 优化UI视觉: 因为网络错误跳过任务后,更新总进度条的进度,速度和剩余时间信息 -- 更新关于界面: 添加了作者的联系方式 - -### v1.0.0 - *2022-03-6* -- 第一个正式版本 -- 所有基本功能都测试可用 +[详细信息](UPDATE.md) ## 🍻 联系方式 欢迎进群讨论程序,漫画,资源分享, 提交问题等等 diff --git a/UPDATE.md b/UPDATE.md new file mode 100644 index 0000000..07d1dd4 --- /dev/null +++ b/UPDATE.md @@ -0,0 +1,52 @@ +## ⚰️ 更新记录 + +### **[v1.3.0](https://github.com/Zeal-L/BiliBili-Manga-Downloader/releases/tag/v1.3.0)** - *2022-08-11* +- 新增功能: + - 二维码登入 + - 利用 [biliplus](https://www.biliplus.com/) 提供的 [ComicWebReader](https://www.biliplus.com/manga/) 在线漫画平台的api来尝试获取未解锁的漫画章节 +- 优化配置: + - 移除保存文件夹名里的漫画ID信息;元数据现在默认保存,并且以此来初始化我的库存 + - 老用户需要重新下载一章漫画,然后把以前下载好的移动到新文件夹中 +- 修复bug: + - 修复个别`png`保存为`jpg`的情况 + - 修复`BiliPlus Cookie`检测可能出现的隐藏`bug` ([#61][i61]) + - 修复`BiliPlus`可以看未解锁的漫画章节,软件无法下载 ([#52][i52]) + +[i52]: https://github.com/Zeal-L/BiliBili-Manga-Downloader/issues/52 +[i61]: https://github.com/Zeal-L/BiliBili-Manga-Downloader/issues/61 + +### **[v1.2.0-alpha](https://github.com/Zeal-L/BiliBili-Manga-Downloader/releases/tag/v1.2.0-alpha)** - *2022-07-4* +- 非生产准备就绪的实验性版本 +- 测试 `Biliplus Api` + +### **[v1.2.0](https://github.com/Zeal-L/BiliBili-Manga-Downloader/releases/tag/v1.2.0)** - *2022-06-20* +- 新增功能: 现在可以一键保存漫画的元数据了,包括漫画封面,漫画信息, 等等 (json格式) ([#39][i39]) +- 重大优化: 对于 `文件夹-图片形式` 和 `7z压缩包` 的保存方式取消了对漫画原图像的二次压缩,现在图像保存的质量和原图一致。~~(虽然用肉眼看不出来)~~ 由于 `PDF` 保存格式的特殊性,仍然会进行二次压缩和信道转换 + +[i39]: https://github.com/Zeal-L/BiliBili-Manga-Downloader/issues/39 + +### **[v1.1.0](https://github.com/Zeal-L/BiliBili-Manga-Downloader/releases/tag/v1.1.0)** - *2022-05-19* +- 新增功能: 添加了多种主题选择 +- 新增功能: 一键检查软件更新 +- 修复bug: 修复了一个可能会导致启动失败的保存路径设置;现在如果保存的路径意外失效会初始化为默认路径(cwd) + +### **[v1.0.4](https://github.com/Zeal-L/BiliBili-Manga-Downloader/releases/tag/v1.0.4)** - *2022-04-24* +- 优化使用体验: 我的库存列表现在按照漫画名排序 +- 优化项目结构: 重新分类了原始UI文件和资源文件,并更新了打包脚本 +- 更新依赖项:更新了过去一个月积攒的 pyside6,pillow 等 python 库的新版本 + +### **[v1.0.3](https://github.com/Zeal-L/BiliBili-Manga-Downloader/releases/tag/v1.0.3)** - *2022-03-12* +- 修复bug: 使用7z保存时可能会因为文件名特殊字符引起报错;添加针对“.”的正则过滤 +- 优化配置: 全局网络请求的 timeout 以及 max retry 值增大一倍 + +### **[v1.0.2](https://github.com/Zeal-L/BiliBili-Manga-Downloader/releases/tag/v1.0.2)** - *2022-03-11* +- 修复bug: 总进度条数值错误更新为0 +- 优化设置选项: 以防在网络不好的情况下高线程数导致的频繁重试警告,最大线程数现在设为32 + +### **[v1.0.1](https://github.com/Zeal-L/BiliBili-Manga-Downloader/releases/tag/v1.0.1)** - *2022-03-9* +- 优化UI视觉: 因为网络错误跳过任务后,更新总进度条的进度,速度和剩余时间信息 +- 更新关于界面: 添加了作者的联系方式 + +### **[v1.0.0](https://github.com/Zeal-L/BiliBili-Manga-Downloader/releases/tag/v1.0.0)** - *2022-03-6* +- 第一个正式版本 +- 所有基本功能都测试可用 \ No newline at end of file diff --git a/src/Episode.py b/src/Episode.py index 7a56b7d..363f7c7 100644 --- a/src/Episode.py +++ b/src/Episode.py @@ -203,6 +203,7 @@ def download( "rate": -1, } ) + return # ?########################################################### # ? 下载所有图片 From 29d6347e1f912c58f64f841707323d3bdff58733 Mon Sep 17 00:00:00 2001 From: Zeal-L Date: Sun, 20 Aug 2023 11:15:13 +1000 Subject: [PATCH 3/8] =?UTF-8?q?:art:=20=E6=8A=8A=20pylint=20=E5=BE=97?= =?UTF-8?q?=E5=88=86=E4=BC=98=E5=8C=96=E5=88=B09+?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/pylint.yml | 24 +++++- .pylintrc | 9 ++- UPDATE.md | 2 +- app.py | 4 + linting.py | 21 ++++++ src/BiliPlus.py | 27 ++++--- src/BiliQrCode.py | 5 ++ src/Comic.py | 10 ++- src/SearchComic.py | 4 + src/Utils.py | 26 ++++--- src/ui/MainGUI.py | 13 +++- src/ui/MangaUI.py | 137 ++++++++++++++++++++++------------- src/ui/MyAboutUI.py | 4 + src/ui/QrCodeUI.py | 4 + src/ui/SettingUI.py | 25 ++++--- 15 files changed, 219 insertions(+), 96 deletions(-) create mode 100644 linting.py diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index 3585dd8..350ba63 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -14,10 +14,28 @@ jobs: uses: actions/setup-python@v3 with: python-version: ${{ matrix.python-version }} + - name: Install dependencies run: | python -m pip install --upgrade pip sh setup.sh - - name: Analysing the code with pylint - run: | - pipenv run pylint $(git ls-files '*.py' | grep -v "PySide_src") \ No newline at end of file + + # - name: Analysing the code with pylint + # id: pylint + # run: | + # pipenv run pylint $(git ls-files '*.py' | grep -v "PySide_src") + + - name: Pylinting + run: pipenv run python3 linting.py + id: linting + + - name: Low score warning + if: steps.linting.outputs.score < 9 + run: echo "Score was too low." && exit 1 + + - name: Passed + if: steps.linting.outputs.score >= 9 + run: echo "All passed." + + - name: Show score + run: echo "Your score is ${{ steps.linting.outputs.score }}." \ No newline at end of file diff --git a/.pylintrc b/.pylintrc index fe0793d..efbbb3b 100644 --- a/.pylintrc +++ b/.pylintrc @@ -16,7 +16,7 @@ clear-cache-post-run=no # In error mode, messages with a category besides ERROR or FATAL are # suppressed, and no reports are done by default. Error mode is compatible with # disabling specific errors. -errors-only=yes +#errors-only= # Always return a 0 (non-error) status code, even if lint errors are found. # This is primarily useful in continuous integration scripts. @@ -69,7 +69,6 @@ ignored-modules= # pygtk.require(). init-hook="from pylint.config import find_pylintrc;import os, sys; sys.path.append(os.path.dirname(find_pylintrc()))" - # Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the # number of processors available to use, and will cap the count on Windows to # avoid hangs. @@ -338,7 +337,7 @@ indent-after-paren=4 indent-string=' ' # Maximum number of characters on a single line. -max-line-length=100 +max-line-length=120 # Maximum number of lines in a module. max-module-lines=1000 @@ -429,7 +428,9 @@ disable=raw-checker-failed, suppressed-message, useless-suppression, deprecated-pragma, - use-symbolic-message-instead + use-symbolic-message-instead, + logging-fstring-interpolation, + invalid-name # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option diff --git a/UPDATE.md b/UPDATE.md index 07d1dd4..3d50cb8 100644 --- a/UPDATE.md +++ b/UPDATE.md @@ -3,7 +3,7 @@ ### **[v1.3.0](https://github.com/Zeal-L/BiliBili-Manga-Downloader/releases/tag/v1.3.0)** - *2022-08-11* - 新增功能: - 二维码登入 - - 利用 [biliplus](https://www.biliplus.com/) 提供的 [ComicWebReader](https://www.biliplus.com/manga/) 在线漫画平台的api来尝试获取未解锁的漫画章节 + - 利用 [Biliplus](https://www.biliplus.com/) 提供的 [ComicWebReader](https://www.biliplus.com/manga/) 在线漫画平台的api来尝试获取未解锁的漫画章节 - 优化配置: - 移除保存文件夹名里的漫画ID信息;元数据现在默认保存,并且以此来初始化我的库存 - 老用户需要重新下载一章漫画,然后把以前下载好的移动到新文件夹中 diff --git a/app.py b/app.py index 24ff374..145a87a 100644 --- a/app.py +++ b/app.py @@ -1,3 +1,7 @@ +""" +这是一个哔哩哔哩漫画下载器的应用程序,它提供了一个GUI界面,可以让用户下载漫画。这个文件是程序的入口文件,用于启动程序。 +""" + from sys import argv, exit from ctypes import windll diff --git a/linting.py b/linting.py new file mode 100644 index 0000000..450eee8 --- /dev/null +++ b/linting.py @@ -0,0 +1,21 @@ +"""Linting and return score as system code""" + +import subprocess +from os import system +from pylint.lint import Run + + +command1 = ['git', 'ls-files', '*.py'] +command2 = ['grep', '-v', 'PySide_src'] +p1 = subprocess.Popen(command1, stdout=subprocess.PIPE) +p2 = subprocess.Popen(command2, stdin=p1.stdout, stdout=subprocess.PIPE) +p1.stdout.close() +output, error = p2.communicate() + +file_paths = [path for path in output.decode().split('\n') if path] + + +results = Run(file_paths, do_exit=False) +score = round(getattr(results.linter.stats, "global_note", 0), 2) + +system(f'echo "::set-output name=score::{str(score)}"') \ No newline at end of file diff --git a/src/BiliPlus.py b/src/BiliPlus.py index d73ffa7..e713fe2 100644 --- a/src/BiliPlus.py +++ b/src/BiliPlus.py @@ -1,3 +1,7 @@ +""" +该模块包含了BiliPlusComic和BiliPlusEpisode类,用于获取BiliPlus网站上的单本漫画信息和章节信息 +""" + from __future__ import annotations from typing import TYPE_CHECKING @@ -38,9 +42,9 @@ def getEpisodesInfo(self) -> list[Episode]: return [] # ?########################################################### - # ? 解析章节 - ep_list = self.data["ep_list"] - for episode in reversed(ep_list): + # ? 解析 Biliplus 章节 + biliplus_ep_list = self.data["ep_list"] + for episode in reversed(biliplus_ep_list): epi = BiliPlusEpisode( episode, self.sessdata, @@ -80,7 +84,7 @@ def _(url: str = biliplus_detail_url) -> dict: raise e if "未登录" in res.text: self.mainGUI.signal_message_box.emit("请先在设置界面填写正确的BiliPlus Cookie!") - return + return {} if res.status_code != 200: logger.warning( f"漫画id:{self.comic_id} 在BiliPlus爬取漫画信息失败! 状态码:{res.status_code}, 理由: {res.reason} 重试中..." @@ -120,7 +124,7 @@ def _(url: str = biliplus_detail_url) -> dict: for ep in episodes: if str(ep.id) in ep_available: ep.available = True - except Exception as e: + except requests.RequestException as e: logger.error(f"漫画id:{self.comic_id} 在处理BiliPlus解锁章节数据时失败!\n{e}") logger.exception(e) self.mainGUI.signal_message_box.emit( @@ -174,7 +178,8 @@ def _() -> list[dict]: raise e if res.status_code != 200: logger.warning( - f"《{self.comic_name}》章节:{self.title} 从BiliPlus获取图片列表失败! 状态码:{res.status_code}, 理由: {res.reason} 重试中..." + f"《{self.comic_name}》章节:{self.title} 从BiliPlus获取图片列表失败! " + f"状态码:{res.status_code}, 理由: {res.reason} 重试中..." ) raise requests.HTTPError() return res.text @@ -187,7 +192,10 @@ def _() -> list[dict]: ) logger.exception(e) self.mainGUI.signal_message_box.emit( - f"《{self.comic_name}》章节:{self.title} 从BiliPlus重复获取图片列表多次后失败!\n已暂时跳过此章节!\n请检查网络连接或者重启软件!\n\n更多详细信息请查看日志文件, 或联系开发者!" + f"《{self.comic_name}》章节:{self.title} 从BiliPlus重复获取图片列表多次后失败!\n" + f"已暂时跳过此章节!\n" + f"请检查网络连接或者重启软件!\n\n" + f"更多详细信息请查看日志文件, 或联系开发者!" ) return False @@ -210,13 +218,14 @@ def _() -> list[dict]: f"《{self.comic_name}》章节:{self.title} 在处理BiliPlus解锁章节图片地址时因Cookie有误导致失败!" ) return False - except Exception as e: + except requests.RequestException as e: logger.error( f"《{self.comic_name}》章节:{self.title} 在处理BiliPlus解锁章节图片地址时失败!\n{e}" ) logger.exception(e) self.mainGUI.signal_message_box.emit( - f"《{self.comic_name}》章节:{self.title} 在处理BiliPlus解锁章节图片地址时失败!\n\n更多详细信息请查看日志文件, 或联系开发者!" + f"《{self.comic_name}》章节:{self.title} 在处理BiliPlus解锁章节图片地址时失败!\n\n" + f"更多详细信息请查看日志文件, 或联系开发者!" ) return False diff --git a/src/BiliQrCode.py b/src/BiliQrCode.py index d399794..b275a4d 100644 --- a/src/BiliQrCode.py +++ b/src/BiliQrCode.py @@ -1,3 +1,7 @@ +""" +该模块提供了一个用于生成Bilibili扫码登录二维码的类QrCode,以及确认登录和获取cookie的方法 +""" + from __future__ import annotations import io @@ -17,6 +21,7 @@ class QrCode: + """Bilibili 扫码登录二维码类""" def __init__(self, mainGUI: MainGUI) -> None: self.mainGUI = mainGUI self.generate_url = ( diff --git a/src/Comic.py b/src/Comic.py index ae8a315..469bfbc 100644 --- a/src/Comic.py +++ b/src/Comic.py @@ -1,3 +1,7 @@ +""" +该模块包含了单本漫画的综合信息类Comic,以及与Comic相关的函数 +""" + from __future__ import annotations import re @@ -129,9 +133,11 @@ def _() -> bytes: except RetryError as e: logger.error(f"获取封面图片多次后失败,跳过!\n{e}") self.mainGUI.signal_message_box.emit( - f"获取封面图片多次后失败!\n请检查网络连接或者重启软件!\n\n更多详细信息请查看日志文件, 或联系开发者!" + "获取封面图片多次后失败!\n" + "请检查网络连接或者重启软件!\n\n" + "更多详细信息请查看日志文件, 或联系开发者!" ) - return open(":/imgs/fail_img.jpg") + return open(":/imgs/fail_img.jpg", encoding="utf-8") ############################################################ def getEpisodesInfo(self) -> list[Episode]: diff --git a/src/SearchComic.py b/src/SearchComic.py index 557ac7f..f948578 100644 --- a/src/SearchComic.py +++ b/src/SearchComic.py @@ -1,3 +1,7 @@ +""" +该模块包含一个用于根据漫画名搜索漫画信息的类SearchComic +""" + from __future__ import annotations from typing import TYPE_CHECKING diff --git a/src/Utils.py b/src/Utils.py index e0f19e6..d7b35a1 100644 --- a/src/Utils.py +++ b/src/Utils.py @@ -1,3 +1,7 @@ +""" +该模块包含了一些工具函数和类,用于支持BiliBili漫画下载器的其他模块 +""" + from __future__ import annotations import ctypes @@ -291,14 +295,16 @@ def formatSpeed(self, speed: float) -> str: """ if speed < 0: return "0B/s" - elif speed < 1024: - return "%dB/s" % speed - elif speed < 1024 * 1024: - return "%.2fKB/s" % (speed / 1024) - elif speed < 1024 * 1024 * 1024: - return "%.2fMB/s" % (speed / 1024 / 1024) - else: - return "%.2fGB/s" % (speed / 1024 / 1024 / 1024) + if speed < 1024: + return f"{speed:.0f}B/s" + if speed < 1024 * 1024: + return f"{speed / 1024:.2f}KB/s" + if speed < 1024 * 1024 * 1024: + return f"{speed / 1024 / 1024:.2f}MB/s" + if speed < 1024 * 1024 * 1024 * 1024: + return f"{speed / 1024 / 1024 / 1024:.2f}GB/s" + + return f"{speed / 1024 / 1024 / 1024 / 1024:.2f}TB/s" ############################################################ def getRemainingTimeStr(self, task_id: int) -> str | None: @@ -344,8 +350,8 @@ def formatTime(self, seconds: float) -> str: m, s = divmod(seconds, 60) h, m = divmod(m, 60) if h > 24: - return "%d天 %02d:%02d:%02d" % (h // 24, h % 24, m, s) - return "%02d:%02d:%02d" % (h, m, s) + return f"{h // 24}天 {h % 24:02}:{m:02}:{s:02}" + return f"{h:02}:{m:02}:{s:02}" def checkNewVersion(mainGUI: MainGUI): diff --git a/src/ui/MainGUI.py b/src/ui/MainGUI.py index cebd086..1be88d0 100644 --- a/src/ui/MainGUI.py +++ b/src/ui/MainGUI.py @@ -1,6 +1,11 @@ +""" +该模块包含了主窗口类,用于管理所有子UI +""" + import json import logging import os +from functools import partial from typing import Any from PySide6.QtCore import Signal @@ -31,10 +36,10 @@ def __init__(self, app): self.setupUi(self) self.setWindowTitle(f"哔哩哔哩漫画下载器 v{__version__}") self.setFont(QFont("Microsoft YaHei", 10)) - self.signal_message_box.connect(lambda msg: QMessageBox.warning(None, "警告", msg)) - self.signal_resolve_status.connect( - lambda status: self.label_resolve_status.setText(status) + self.signal_message_box.connect( + lambda msg: QMessageBox.warning(None, "警告", msg) ) + self.signal_resolve_status.connect(partial(self.label_resolve_status.setText)) logger.info("\n\n\t\t\t------------------- 程序启动,初始化主窗口 -------------------\n") @@ -79,7 +84,7 @@ def _(path: str) -> None: os.remove(file_path) os.rmdir(path) - if self.settingUI.clearUserData: + if self.settingUI.clear_user_data: try: _(self.app_folder) except OSError as e: diff --git a/src/ui/MangaUI.py b/src/ui/MangaUI.py index d004dc1..0b22bc4 100644 --- a/src/ui/MangaUI.py +++ b/src/ui/MangaUI.py @@ -1,3 +1,7 @@ +""" +漫画UI类,用于搜索、下载、管理漫画 +""" + from __future__ import annotations import json @@ -54,6 +58,8 @@ def __init__(self, mainGUI: MainGUI): self.init_mangaDetails(mainGUI) self.init_myLibrary(mainGUI) self.init_episodesDetails(mainGUI) + self.init_episodesDownloadSelected(mainGUI) + self.init_episodesResolve(mainGUI) self.executor = ThreadPoolExecutor() ############################################################ @@ -215,9 +221,9 @@ def updateMyLibrary(self, mainGUI: MainGUI) -> bool: self.updateMyLibrarySingle, mainGUI, comic_id, - my_library[comic_id]["comic_path"], + comic_info["comic_path"], ) - for comic_id in my_library + for comic_id, comic_info in my_library.items() ) if fail_comic := [ @@ -558,6 +564,27 @@ def updateComicList(self, info: dict) -> None: mainGUI.signal_resolve_status.emit("") ############################################################ + + def checkbox_change_callBack(self, mainGUI: MainGUI, item: QListWidgetItem) -> None: + """章节详情界面的多选框状态改变时的回调函数 + + Args: + mainGUI (MainGUI): 主窗口类实例 + item (QListWidgetItem): 被点击的item + """ + # 暂停itemChanged 信号,防止死循环 + mainGUI.listWidget_chp_detail.itemChanged.disconnect() + if item.flags() == Qt.NoItemFlags: + return + if item.checkState() == Qt.Checked: + self.num_selected += 1 + elif item.checkState() == Qt.Unchecked: + self.num_selected -= 1 + mainGUI.label_chp_detail_num_selected.setText(f"已选中:{self.num_selected}") + mainGUI.listWidget_chp_detail.itemChanged.connect(self.checkbox_change_callBack) + + ############################################################ + def init_episodesDetails(self, mainGUI: MainGUI) -> None: """绑定章节界面的多选以及右键菜单事件 @@ -569,19 +596,7 @@ def init_episodesDetails(self, mainGUI: MainGUI) -> None: # ?########################################################### # ? 绑定鼠标点击选择信号 - def checkbox_change(item: QListWidgetItem) -> None: - # 暂停itemChanged 信号,防止死循环 - mainGUI.listWidget_chp_detail.itemChanged.disconnect() - if item.flags() == Qt.NoItemFlags: - return - if item.checkState() == Qt.Checked: - self.num_selected += 1 - elif item.checkState() == Qt.Unchecked: - self.num_selected -= 1 - mainGUI.label_chp_detail_num_selected.setText(f"已选中:{self.num_selected}") - mainGUI.listWidget_chp_detail.itemChanged.connect(checkbox_change) - - mainGUI.listWidget_chp_detail.itemChanged.connect(checkbox_change) + mainGUI.listWidget_chp_detail.itemChanged.connect(self.checkbox_change_callBack) def _(item: QListWidgetItem) -> None: mainGUI.listWidget_chp_detail.itemChanged.disconnect() @@ -594,7 +609,7 @@ def _(item: QListWidgetItem) -> None: item.setCheckState(Qt.Checked) self.num_selected += 1 mainGUI.label_chp_detail_num_selected.setText(f"已选中:{self.num_selected}") - mainGUI.listWidget_chp_detail.itemChanged.connect(checkbox_change) + mainGUI.listWidget_chp_detail.itemChanged.connect(self.checkbox_change_callBack) mainGUI.listWidget_chp_detail.itemPressed.connect(_) @@ -608,7 +623,7 @@ def checkSelected() -> None: item.setCheckState(Qt.Checked) self.num_selected += 1 mainGUI.label_chp_detail_num_selected.setText(f"已选中:{self.num_selected}") - mainGUI.listWidget_chp_detail.itemChanged.connect(checkbox_change) + mainGUI.listWidget_chp_detail.itemChanged.connect(self.checkbox_change_callBack) def uncheckSelected() -> None: mainGUI.listWidget_chp_detail.itemChanged.disconnect() @@ -617,7 +632,7 @@ def uncheckSelected() -> None: item.setCheckState(Qt.Unchecked) self.num_selected -= 1 mainGUI.label_chp_detail_num_selected.setText(f"已选中:{self.num_selected}") - mainGUI.listWidget_chp_detail.itemChanged.connect(checkbox_change) + mainGUI.listWidget_chp_detail.itemChanged.connect(self.checkbox_change_callBack) def checkAll() -> None: mainGUI.listWidget_chp_detail.itemChanged.disconnect() @@ -627,7 +642,7 @@ def checkAll() -> None: mainGUI.listWidget_chp_detail.item(i).setCheckState(Qt.Checked) self.num_selected += 1 mainGUI.label_chp_detail_num_selected.setText(f"已选中:{self.num_selected}") - mainGUI.listWidget_chp_detail.itemChanged.connect(checkbox_change) + mainGUI.listWidget_chp_detail.itemChanged.connect(self.checkbox_change_callBack) def uncheckAll() -> None: mainGUI.listWidget_chp_detail.itemChanged.disconnect() @@ -636,7 +651,7 @@ def uncheckAll() -> None: if mainGUI.listWidget_chp_detail.item(i).flags() != Qt.NoItemFlags: mainGUI.listWidget_chp_detail.item(i).setCheckState(Qt.Unchecked) mainGUI.label_chp_detail_num_selected.setText(f"已选中:{self.num_selected}") - mainGUI.listWidget_chp_detail.itemChanged.connect(checkbox_change) + mainGUI.listWidget_chp_detail.itemChanged.connect(self.checkbox_change_callBack) def myMenu(pos: QPoint) -> None: menu = QMenu() @@ -649,6 +664,55 @@ def myMenu(pos: QPoint) -> None: mainGUI.listWidget_chp_detail.setContextMenuPolicy(Qt.CustomContextMenu) mainGUI.listWidget_chp_detail.customContextMenuRequested.connect(myMenu) + + ############################################################ + + def init_episodesResolve(self, mainGUI: MainGUI) -> None: + """绑定章节界面的解析按钮事件 + + Args: + mainGUI (MainGUI): 主窗口类实例 + """ + + # ?########################################################### + # ? 绑定B站解析按钮事件 + def _() -> None: + if self.present_comic_id == 0: + QMessageBox.critical(mainGUI, "警告", "请先在搜索或库存列表选择一个漫画!") + return + if not mainGUI.getConfig("cookie"): + QMessageBox.critical(mainGUI, "警告", "请先在设置界面填写自己的Cookie!") + return + self.resolveEnable(mainGUI, "resolving") + comic = Comic(self.present_comic_id, mainGUI) + self.updateComicInfoEvent(mainGUI, comic, "bilibili") + + mainGUI.pushButton_resolve_detail.clicked.connect(_) + + # ?########################################################### + # ? 绑定BiliPlus解析按钮事件 + def _() -> None: + if self.present_comic_id == 0: + QMessageBox.critical(mainGUI, "警告", "请先在搜索或库存列表选择一个漫画!") + return + if not mainGUI.getConfig("biliplus_cookie"): + QMessageBox.critical(mainGUI, "警告", "请先在设置界面填写自己的BiliPlus Cookie!") + return + self.resolveEnable(mainGUI, "resolving") + comic = BiliPlusComic(self.present_comic_id, mainGUI) + self.updateComicInfoEvent(mainGUI, comic, "biliplus") + + mainGUI.pushButton_biliplus_resolve_detail.clicked.connect(_) + + ############################################################ + + def init_episodesDownloadSelected(self, mainGUI: MainGUI) -> None: + """初始化章节详情界面的下载选中章节按钮事件 + + Args: + mainGUI (MainGUI): 主窗口类实例 + """ + # ?########################################################### # ? 绑定下载选中章节事件 def _() -> None: @@ -688,7 +752,7 @@ def _() -> None: mainGUI.downloadUI.addTask(mainGUI, self.epi_list[i]) item.setFlags(Qt.NoItemFlags) item.setBackground(QColor(0, 255, 0, 50)) - mainGUI.listWidget_chp_detail.itemChanged.connect(checkbox_change) + mainGUI.listWidget_chp_detail.itemChanged.connect(self.checkbox_change_callBack) # ?########################################################### # ? 更新我的库存界面信息 也就是v_Layout_myLibrary里的章节数量信息 @@ -708,37 +772,8 @@ def _() -> None: mainGUI.pushButton_chp_detail_download_selected.clicked.connect(_) mainGUI.pushButton_biliplus_detail_download_selected.clicked.connect(_) - # ?########################################################### - # ? 绑定B站解析按钮事件 - def _() -> None: - if self.present_comic_id == 0: - QMessageBox.critical(mainGUI, "警告", "请先在搜索或库存列表选择一个漫画!") - return - if not mainGUI.getConfig("cookie"): - QMessageBox.critical(mainGUI, "警告", "请先在设置界面填写自己的Cookie!") - return - self.resolveEnable(mainGUI, "resolving") - comic = Comic(self.present_comic_id, mainGUI) - self.updateComicInfoEvent(mainGUI, comic, "bilibili") - - mainGUI.pushButton_resolve_detail.clicked.connect(_) - - # ?########################################################### - # ? 绑定BiliPlus解析按钮事件 - def _() -> None: - if self.present_comic_id == 0: - QMessageBox.critical(mainGUI, "警告", "请先在搜索或库存列表选择一个漫画!") - return - if not mainGUI.getConfig("biliplus_cookie"): - QMessageBox.critical(mainGUI, "警告", "请先在设置界面填写自己的BiliPlus Cookie!") - return - self.resolveEnable(mainGUI, "resolving") - comic = BiliPlusComic(self.present_comic_id, mainGUI) - self.updateComicInfoEvent(mainGUI, comic, "biliplus") - - mainGUI.pushButton_biliplus_resolve_detail.clicked.connect(_) - ########################################################### + def resolveEnable(self, mainGUI: MainGUI, resolve_type: str) -> None: # sourcery skip: extract-duplicate-method, switch """根据解析状态对按钮进行允许和禁用状态的改变 diff --git a/src/ui/MyAboutUI.py b/src/ui/MyAboutUI.py index 2dc3452..fe229b5 100644 --- a/src/ui/MyAboutUI.py +++ b/src/ui/MyAboutUI.py @@ -1,3 +1,7 @@ +""" +该模块包含了关于窗口类MyAboutUI的定义,用于显示关于窗口 +""" + from PySide6.QtCore import Qt from PySide6.QtWidgets import QWidget diff --git a/src/ui/QrCodeUI.py b/src/ui/QrCodeUI.py index 539e0b4..dcb819e 100644 --- a/src/ui/QrCodeUI.py +++ b/src/ui/QrCodeUI.py @@ -1,3 +1,7 @@ +""" +该模块包含QrCodeUI类,它是一个用于显示QR码的窗口 +""" + from PySide6.QtCore import Qt from PySide6.QtWidgets import QWidget diff --git a/src/ui/SettingUI.py b/src/ui/SettingUI.py index e9a8155..938c01a 100644 --- a/src/ui/SettingUI.py +++ b/src/ui/SettingUI.py @@ -1,3 +1,7 @@ +""" +该模块包含SettingUI类,它管理BiliBili Manga Downloader应用程序的设置UI。它允许用户设置 BiliBili 登录 cookie、下载路径、线程数和其他设置 +""" + from __future__ import annotations import os @@ -31,14 +35,13 @@ class SettingUI(QObject): """设置窗口类,用于管理设置UI""" - # ?########################################################### # ? 用于多线程更新我的库存 signal_qr_res = Signal(dict) def __init__(self, mainGUI: MainGUI): super().__init__() self.mainGUI = mainGUI - self.clearUserData = False + self.clear_user_data = False self.init_qrCode(mainGUI) self.init_cookie(mainGUI) self.init_biliplus_cookie(mainGUI) @@ -54,6 +57,11 @@ def __init__(self, mainGUI: MainGUI): ############################################################ def qrCodeCallBack(self, data: dict) -> None: + """二维码回调函数 + + Args: + data (dict): 二维码数据 + """ # sourcery skip: extract-method if data is None: @@ -156,9 +164,6 @@ def is_cookie_valid(self, mainGUI: MainGUI, cookie: str) -> bool: """ detail_url = "https://manga.bilibili.com/twirp/comic.v1.Comic/Search?device=pc&platform=web" headers = { - "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36", - "origin": "https://manga.bilibili.com", - "referer": "https://manga.bilibili.com/search?from=manga_homepage", "cookie": f"SESSDATA={cookie}", } payload = {"key_word": "test", "page_num": 1, "page_size": 1} @@ -231,19 +236,15 @@ def is_biliplus_cookie_valid(self, mainGUI: MainGUI, cookie: str) -> bool: # 此处对Cookie是否有效验证使用了硬编码,如果该漫画或该章节变更,需要修改才能继续正常验证 main_url = "https://www.biliplus.com/manga/?act=read&mangaid=26551&epid=316882" headers = { - "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36", "cookie": f"login=2;access_key={cookie}", } - payload = {} @retry( stop_max_delay=MAX_RETRY_SMALL, wait_exponential_multiplier=RETRY_WAIT_EX ) def _() -> None: try: - res = requests.post( - main_url, data=payload, headers=headers, timeout=TIMEOUT_SMALL - ) + res = requests.post(main_url, headers=headers, timeout=TIMEOUT_SMALL) except requests.RequestException as e: logger.warning(f"测试BiliPlus Cookie是否有效失败! 重试中...\n{e}") raise e @@ -255,7 +256,7 @@ def _() -> None: if "hoz-container" not in res.text: logger.warning("BiliPlus Cookie检测出现故障,暂时无法检测是否有效...") raise ReferenceError - elif 'class="comic-single"' not in res.text: + if 'class="comic-single"' not in res.text: logger.warning("BiliPlus Cookie无效!重试中...") raise requests.HTTPError() @@ -367,7 +368,7 @@ def _(): QMessageBox.Yes | QMessageBox.No, ) if res == QMessageBox.Yes: - self.clearUserData = True + self.clear_user_data = True mainGUI.pushButton_clear_data.clicked.connect(_) From 8755873434002c74df5afb1860799e822f2bdb52 Mon Sep 17 00:00:00 2001 From: Zeal-L Date: Sun, 20 Aug 2023 13:14:23 +1000 Subject: [PATCH 4/8] =?UTF-8?q?:ambulance:=20=E4=BF=AE=E5=A4=8D=20pylint?= =?UTF-8?q?=20=E5=B7=A5=E4=BD=9C=E6=B5=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/pylint.yml | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index 350ba63..63a9298 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -4,7 +4,7 @@ on: [push] jobs: build: - runs-on: windows-latest + runs-on: ubuntu-latest strategy: matrix: python-version: ["3.11"] @@ -20,22 +20,8 @@ jobs: python -m pip install --upgrade pip sh setup.sh - # - name: Analysing the code with pylint - # id: pylint - # run: | - # pipenv run pylint $(git ls-files '*.py' | grep -v "PySide_src") - - - name: Pylinting - run: pipenv run python3 linting.py - id: linting - - - name: Low score warning - if: steps.linting.outputs.score < 9 - run: echo "Score was too low." && exit 1 - - - name: Passed - if: steps.linting.outputs.score >= 9 - run: echo "All passed." - - - name: Show score - run: echo "Your score is ${{ steps.linting.outputs.score }}." \ No newline at end of file + - name: Analysing the code with pylint + id: pylint + run: | + pipenv run pylint $(git ls-files '*.py' | grep -v "PySide_src") + continue-on-error: true \ No newline at end of file From f2082685143eebbe2367e38136afe6372de10651 Mon Sep 17 00:00:00 2001 From: Zeal-L Date: Sun, 20 Aug 2023 14:02:55 +1000 Subject: [PATCH 5/8] =?UTF-8?q?:zap:=20=E9=87=8D=E6=9E=84=E4=B8=8B?= =?UTF-8?q?=E8=BD=BD=E4=BB=BB=E5=8A=A1=E7=9B=B8=E5=85=B3=E7=9A=84=E9=80=BB?= =?UTF-8?q?=E8=BE=91=EF=BC=8C=E5=87=8F=E5=B0=91=E8=80=A6=E5=90=88=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/DownloadManager.py | 258 +++++++++++++++++++++++++++++++++++++++++ src/Episode.py | 98 +++++----------- src/Utils.py | 195 ------------------------------- src/ui/DownloadUI.py | 84 +++++++------- 4 files changed, 328 insertions(+), 307 deletions(-) create mode 100644 src/DownloadManager.py diff --git a/src/DownloadManager.py b/src/DownloadManager.py new file mode 100644 index 0000000..03448a3 --- /dev/null +++ b/src/DownloadManager.py @@ -0,0 +1,258 @@ +""" +该模块包含了一个下载管理器类,用于管理漫画下载任务的创建、更新和删除等操作 +""" + +import time +from concurrent.futures import ThreadPoolExecutor + +from PySide6.QtCore import SignalInstance + +from src.Episode import Episode + + +class DownloadManager: + """下载管理器类,用于管理漫画下载任务的创建、更新和删除等操作""" + + def __init__( + self, + max_workers: int, + save_method: str, + signal_rate_progress: SignalInstance, + signal_message_box: SignalInstance, + ) -> None: + self.id_count = 0 + self.save_method = save_method + self.executor = ThreadPoolExecutor(max_workers=max_workers) + self.signal_rate_progress = signal_rate_progress + self.signal_message_box = signal_message_box + + self.all_tasks = {} + self.avg_speed_in_last_five_sec = {} + + ############################################################ + + def createEpisodeTask(self, epi: Episode) -> int: + """创建一个新的漫画章节任务并将其添加到所有任务列表中 + + Args: + epi (Episode): 要下载的章节 + + Returns: + int: 新创建任务的ID。 + """ + self.all_tasks[self.id_count] = { + "size": epi.size, + "curr_rate": 0.0, + "last_rate": 0.0, + "last_time": time.perf_counter(), + "curr_speed": 0.1, + "avg_speed": 0.1, + "future": self.executor.submit( + self.__thread__EpisodeTask, self.id_count, epi + ), + } + self.id_count += 1 + return self.id_count - 1 + + ############################################################ + + def updateTaskInfo(self, curr_id: int, rate: float) -> None: + """更新任务信息,包括当前速度、当前下载速率、上一次下载速率和上一次更新时间 + + Args: + curr_id (int): 当前任务的ID + rate (float): 下载进度百分比 + """ + task: dict = self.all_tasks[curr_id] + curr_time = time.perf_counter() + + task["curr_speed"] = ( + task["size"] * rate - task["size"] * task["last_rate"] + ) / (curr_time - task["last_time"]) + + task["curr_rate"] = rate + task["last_rate"] = task["curr_rate"] + task["last_time"] = curr_time + + ############################################################ + + def getTotalRate(self) -> float: + """获取所有任务的平均下载进度""" + return ( + sum(task["curr_rate"] for task in self.all_tasks.values()) + / len(self.all_tasks) + * 100 + ) + + ############################################################ + + def getTotalSpeed(self) -> float: + """获取所有任务的平均下载速度 + + Returns: + float: 平均下载速度 + """ + self.avg_speed_in_last_five_sec[time.perf_counter()] = sum( + task["curr_speed"] for task in self.all_tasks.values() + ) + + # 取5秒内的平均速度,以防止速度突然变化 + # 比如下载完一个文件 速度突然变为0 + # 或者开始一组新的下载,速度突然变为很大 + for key in list(self.avg_speed_in_last_five_sec.keys()): + if key < time.perf_counter() - 5: + self.avg_speed_in_last_five_sec.pop(key) + + return sum(self.avg_speed_in_last_five_sec.values()) / len( + self.avg_speed_in_last_five_sec + ) + + ############################################################ + + def getTotalSpeedStr(self) -> str: + """获取所有任务的平均下载速度的字符串表示 + + Returns: + str: 平均下载速度的字符串表示 + """ + return self.formatSpeed(self.getTotalSpeed()) + + ############################################################ + + def getTotalRemainedTimeStr(self) -> str: + """获取所有任务的剩余时间的字符串表示 + + Returns: + str: 剩余时间的字符串表示 + """ + total_size_left = sum( + task["size"] * (1 - task["curr_rate"]) for task in self.all_tasks.values() + ) + return self.formatTime(total_size_left / self.getTotalSpeed()) + + ############################################################ + + def __thread__EpisodeTask(self, curr_id: int, epi: Episode) -> None: + """下载漫画章节的线程函数, 会在一个新的线程中运行, 包括下载图片和保存图片任务 + + Args: + curr_id (int): 当前任务的ID + epi (Episode): 要下载的章节 + """ + # ?########################################################### + # ? 初始化下载图片需要的参数 + if not epi.init_imgsList(): + self.reportError(curr_id) + return + + # ?########################################################### + # ? 下载所有图片 + imgs_path = [] + for index, img in enumerate(epi.imgs_token, start=1): + img_url = f"{img['url']}?token={img['token']}" + + img_path = epi.downloadImg(index, img_url) + if img_path is None: + self.reportError(curr_id) + epi.clearAfterSave(imgs_path) + return + + rate = index / len(epi.imgs_token) + self.updateTaskInfo(curr_id, rate) + + imgs_path.append(img_path) + self.signal_rate_progress.emit( + { + "taskID": curr_id, + "rate": int(rate * 100), + } + ) + + # ?########################################################### + # ? 保存图片 + + if self.save_method == "PDF": + epi.saveToPDF(imgs_path) + elif self.save_method == "文件夹-图片": + epi.saveToFolder(imgs_path) + elif self.save_method == "7z压缩包": + epi.saveTo7z(imgs_path) + + epi.clearAfterSave(imgs_path) + + self.clearAfterFinish(curr_id) + + ############################################################ + + def createSCTask(self) -> int: + pass + + ############################################################ + + def thread_SCTask(self) -> None: + pass + + ############################################################ + + def clearAfterFinish(self, curr_id) -> None: + """任务完成后的清理工作 + + Args: + curr_id (int): 当前任务的ID + """ + self.all_tasks.pop(curr_id) + + ############################################################ + + def reportError(self, curr_id: int) -> None: + """任务出错时的处理 + + Args: + curr_id (int): 当前任务的ID + """ + self.signal_rate_progress.emit( + { + "taskID": curr_id, + "rate": -1, + } + ) + self.clearAfterFinish(curr_id) + + ############################################################ + def formatSpeed(self, speed: float) -> str: + """格式化每秒速度大小 + + Args: + size (int): 每秒速度大小 + + Returns: + str: 格式化后的每秒速度大小, 例如: 1.23MB/s + """ + if speed < 0: + return "0B/s" + if speed < 1024: + return f"{speed:.0f}B/s" + if speed < 1024 * 1024: + return f"{speed / 1024:.2f}KB/s" + if speed < 1024 * 1024 * 1024: + return f"{speed / 1024 / 1024:.2f}MB/s" + if speed < 1024 * 1024 * 1024 * 1024: + return f"{speed / 1024 / 1024 / 1024:.2f}GB/s" + + return f"{speed / 1024 / 1024 / 1024 / 1024:.2f}TB/s" + + ############################################################ + def formatTime(self, seconds: float) -> str: + """格式化剩余时间 + + Args: + seconds (float): 剩余时间 + + Returns: + str: 格式化后的剩余时间, 例如: 1天 23:59:59 + """ + m, s = divmod(seconds, 60) + h, m = divmod(m, 60) + if h > 24: + return "%d天 %02d:%02d:%02d" % (h // 24, h % 24, m, s) + return "%02d:%02d:%02d" % (h, m, s) diff --git a/src/Episode.py b/src/Episode.py index 363f7c7..ac9e399 100644 --- a/src/Episode.py +++ b/src/Episode.py @@ -1,3 +1,7 @@ +""" +该模块包含漫画章节类,用于管理漫画章节的详细信息 +""" + from __future__ import annotations import json @@ -11,7 +15,6 @@ from PIL import Image from py7zr import SevenZipFile from PyPDF2 import PdfReader, PdfWriter -from PySide6.QtCore import SignalInstance from retrying import retry from src.Utils import ( @@ -138,7 +141,10 @@ def _() -> list[dict]: logger.error(f"《{self.comic_name}》章节:{self.title} 重复获取图片列表多次后失败!,跳过!\n{e}") logger.exception(e) self.mainGUI.signal_message_box.emit( - f"《{self.comic_name}》章节:{self.title} 重复获取图片列表多次后失败!\n已暂时跳过此章节!\n请检查网络连接或者重启软件!\n\n更多详细信息请查看日志文件, 或联系开发者!" + f"《{self.comic_name}》章节:{self.title} 重复获取图片列表多次后失败!\n" + f"已暂时跳过此章节!\n" + f"请检查网络连接或者重启软件!\n\n" + f"更多详细信息请查看日志文件, 或联系开发者!" ) return False @@ -177,73 +183,16 @@ def _() -> list[dict]: ) logger.exception(e) self.mainGUI.signal_message_box.emit( - f"《{self.comic_name}》章节:{self.title} 重复获取图片token多次后失败!\n已暂时跳过此章节!\n请检查网络连接或者重启软件!\n\n更多详细信息请查看日志文件, 或联系开发者!" + f"《{self.comic_name}》章节:{self.title} 重复获取图片token多次后失败!\n" + f"已暂时跳过此章节!\n请检查网络连接或者重启软件!\n\n" + f"更多详细信息请查看日志文件, 或联系开发者!" ) return False return True ############################################################ - def download( - self, signal_rate_progress: SignalInstance, taskID: str - ) -> None: - """下载章节内所有图片 并合并为PDF - - Args: - rate_progress (SignalInstance): 信号槽,用于更新下载进度条 - taskID (str): 任务ID - """ - # ?########################################################### - # ? 初始化下载图片需要的参数 - if not self.init_imgsList(): - signal_rate_progress.emit( - { - "taskID": taskID, - "rate": -1, - } - ) - return - - # ?########################################################### - # ? 下载所有图片 - imgs_path = [] - for index, img in enumerate(self.imgs_token, start=1): - img_url = f"{img['url']}?token={img['token']}" - - img_path = self.downloadImg(index, img_url) - if img_path is None: - signal_rate_progress.emit( - { - "taskID": taskID, - "rate": -1, - } - ) - self.clearAfterSave(imgs_path) - return - - imgs_path.append(img_path) - signal_rate_progress.emit( - { - "taskID": taskID, - "rate": int((index / len(self.imgs_token)) * 100), - "path": self.epi_path, - } - ) - - # ?########################################################### - # ? 保存图片 - - if self.save_method == "PDF": - self.saveToPDF(imgs_path) - elif self.save_method == "文件夹-图片": - self.saveToFolder(imgs_path) - elif self.save_method == "7z压缩包": - self.saveTo7z(imgs_path) - - self.clearAfterSave(imgs_path) - - ############################################################ def clearAfterSave(self, imgs_path: list[str]) -> None: """删除临时图片, 偶尔会出现删除失败的情况,故给与重试5次 @@ -339,9 +288,9 @@ def saveToFolder(self, imgs_path: list[str]) -> None: @retry(stop_max_attempt_number=5) def _(): try: - for index, path in enumerate(imgs_path, start=1): + for index, img_path in enumerate(imgs_path, start=1): - def jpg_exif(): + def jpg_exif(img_path: str): # 在jpg文件属性中记录章节标题作者和软件版本以及版权信息 exif_data = { "0th": { @@ -356,21 +305,21 @@ def jpg_exif(): } } exif_bytes = piexif.dump(exif_data) - piexif.insert(exif_bytes, path) + piexif.insert(exif_bytes, img_path) - img_format = path.split(".")[-1] + img_format = img_path.split(".")[-1] # 将 exif 数据插入到图像文件中, 如果插入失败则跳过 try: if img_format == "jpg": - jpg_exif() + jpg_exif(img_path) except piexif.InvalidImageDataError as e: - logger.warning(f"Failed to insert exif data for {path}: {e}") + logger.warning(f"Failed to insert exif data for {img_path}: {e}") logger.exception(e) # 复制图片到文件夹 shutil.copy2( - path, + img_path, os.path.join( self.epi_path_folder, f"{str(index).zfill(3)}.{img_format}" ), @@ -457,13 +406,15 @@ def _() -> bytes: raise e if res.status_code != 200: logger.warning( - f"《{self.comic_name}》章节:{self.title} - {index} - {img_url} 获取图片 header 失败! 状态码:{res.status_code}, 理由: {res.reason} 重试中..." + f"《{self.comic_name}》章节:{self.title} - {index} - {img_url} 获取图片 header 失败! " + f"状态码:{res.status_code}, 理由: {res.reason} 重试中..." ) raise requests.HTTPError() isValid, md5 = isCheckSumValid(res.headers["Etag"], res.content) if not isValid: logger.warning( - f"《{self.comic_name}》章节:{self.title} - {index} - {img_url} - 下载内容Checksum不正确! 重试中...\n\t{res.headers['Etag']} ≠ {md5}" + f"《{self.comic_name}》章节:{self.title} - {index} - {img_url} - 下载内容Checksum不正确! 重试中...\n" + f"\t{res.headers['Etag']} ≠ {md5}" ) raise requests.HTTPError() return res.content @@ -504,7 +455,10 @@ def _() -> None: ) logger.exception(e) self.mainGUI.signal_message_box.emit( - f"《{self.comic_name}》章节:{self.title} - {index} - 保存图片多次后失败!\n已暂时跳过此章节, 并删除所有缓存文件!\n请重新尝试或者重启软件!\n\n更多详细信息请查看日志文件, 或联系开发者!" + f"《{self.comic_name}》章节:{self.title} - {index} - 保存图片多次后失败!\n" + f"已暂时跳过此章节, 并删除所有缓存文件!\n" + f"请重新尝试或者重启软件!\n\n" + f"更多详细信息请查看日志文件, 或联系开发者!" ) return None diff --git a/src/Utils.py b/src/Utils.py index d7b35a1..f6e657e 100644 --- a/src/Utils.py +++ b/src/Utils.py @@ -9,7 +9,6 @@ import logging import os import re -import time from logging.handlers import TimedRotatingFileHandler from typing import TYPE_CHECKING @@ -160,200 +159,6 @@ def __inner__openFolderAndSelectItems(path): ############################################################ -class DownloadInfo: - """ - 下载任务的详细信息类,包含任务ID、任务文件进度,任务文件大小等, - 可以计算出当前下载速度、平均速度、剩余时间等信息 - """ - - def __init__(self): - self.info = {} - self.average_speed_in_last_second = {} - - ############################################################ - def removeTask(self, taskID: int) -> None: - """清空任务信息, 释放内存 - - Args: - taskID (int): 任务ID - """ - self.info.pop(taskID) - - ############################################################ - def removeAllTasks(self) -> None: - """清空所有任务信息, 释放内存""" - self.info.clear() - - ############################################################ - def createTask(self, taskID: int, size: int) -> None: - """创建任务信息 - - Args: - taskID (int): 任务ID - size (int): 任务文件大小 单位: Byte - """ - self.info[taskID] = { - "size": size, # 任务文件大小 单位: Byte - "rate": 0, # 当前已下载百分比 - "last_rate": 0, # 上次已下载百分比 - "curr_speed": 0, # 当前速度 单位: Byte/s - "average_speed": 0, # 平均速度 单位: Byte/s - "last_update_time": None, # 上次更新时间 单位: 秒 - } - - ############################################################ - def updateTask(self, taskID: int, rate: float) -> None: - """更新任务信息 - - Args: - taskID (int): 任务ID - rate (float): 下载进度百分比 - """ - - self.info[taskID]["rate"] = rate / 100 - - self.calcuCurrSpeed(self.info[taskID]) - self.calcuSmoothSpeed(self.info[taskID]) - - ############################################################ - def calcuCurrSpeed(self, task: dict) -> None: - """计算任务的当前下载速度 - - Args: - task (dict): 任务信息 - """ - if task["rate"] == 0 or task["last_update_time"] is None: - task["curr_speed"] = 0 - task["last_update_time"] = time.perf_counter() - return - curr_time = time.perf_counter() - - task["curr_speed"] = ( - task["size"] * task["rate"] - task["size"] * task["last_rate"] - ) / (curr_time - task["last_update_time"]) - task["last_rate"] = task["rate"] - task["last_update_time"] = curr_time - - ############################################################ - def calcuSmoothSpeed(self, task: dict) -> None: - """计算任务的平均下载速度 - - Args: - task (dict): 任务信息 - """ - SMOOTHING_FACTOR = 0.005 - # 使用 Exponential moving average 来均衡历史平均速度和当前速度,以防波动过大 - task["average_speed"] = SMOOTHING_FACTOR * task["curr_speed"] + ( - 1 - SMOOTHING_FACTOR - ) * (task["average_speed"] or task["curr_speed"]) - - ############################################################ - def getSmoothSpeed(self, task_id) -> str: - if task_id in self.info: - task = self.info[task_id] - if task["average_speed"]: - return self.formatSpeed(task["average_speed"]) - return "0B/s" - - ############################################################ - def getTotalSmoothSpeed(self) -> int: - self.average_speed_in_last_second[time.perf_counter()] = sum( - task["average_speed"] for task in self.info.values() if task["rate"] != 1.0 - ) - - # 取5秒内的平均速度,以防止速度突然变化 - # 比如下载完一个文件 速度突然变为0 - # 或者开始一组新的下载,速度突然变为很大 - for key in list(self.average_speed_in_last_second.keys()): - if key < time.perf_counter() - 5: - self.average_speed_in_last_second.pop(key) - - return ( - sum(self.average_speed_in_last_second.values()) - / len(self.average_speed_in_last_second) - or 1 - ) - - ############################################################ - def getTotalSmoothSpeedStr(self) -> str: - """获取所有任务的平均速度 - - Returns: - str: 平均速度字符串 - """ - return self.formatSpeed(self.getTotalSmoothSpeed()) - - ############################################################ - def formatSpeed(self, speed: float) -> str: - """格式化每秒速度大小 - - Args: - size (int): 每秒速度大小 - - Returns: - str: 格式化后的每秒速度大小, 例如: 1.23MB/s - """ - if speed < 0: - return "0B/s" - if speed < 1024: - return f"{speed:.0f}B/s" - if speed < 1024 * 1024: - return f"{speed / 1024:.2f}KB/s" - if speed < 1024 * 1024 * 1024: - return f"{speed / 1024 / 1024:.2f}MB/s" - if speed < 1024 * 1024 * 1024 * 1024: - return f"{speed / 1024 / 1024 / 1024:.2f}GB/s" - - return f"{speed / 1024 / 1024 / 1024 / 1024:.2f}TB/s" - - ############################################################ - def getRemainingTimeStr(self, task_id: int) -> str | None: - """获取任务的剩余时间 - - Args: - task_id (int): 任务ID - - Returns: - str or None: 剩余时间字符串, 如果任务不存在则返回None - """ - if task_id in self.info: - task = self.info[task_id] - return self.formatTime( - (task["size"] * (1 - task["rate"])) / task["average_speed"] - ) - return None - - ############################################################ - def getTotalRemainingTimeStr(self) -> str: - """获取所有任务的剩余时间 - - Returns: - str: 剩余时间字符串 - """ - total_size_left = sum( - task["size"] * (1 - task["rate"]) for task in self.info.values() - ) - if self.getTotalSmoothSpeed() == 0: - return self.formatTime(0) - return self.formatTime(total_size_left / self.getTotalSmoothSpeed()) - - ############################################################ - def formatTime(self, seconds: float) -> str: - """格式化剩余时间 - - Args: - seconds (float): 剩余时间 - - Returns: - str: 格式化后的剩余时间, 例如: 1天 23:59:59 - """ - m, s = divmod(seconds, 60) - h, m = divmod(m, 60) - if h > 24: - return f"{h // 24}天 {h % 24:02}:{m:02}:{s:02}" - return f"{h:02}:{m:02}:{s:02}" - - def checkNewVersion(mainGUI: MainGUI): """检查新版本, 如果有新版本则弹出提示 diff --git a/src/ui/DownloadUI.py b/src/ui/DownloadUI.py index 33ba326..f6a365d 100644 --- a/src/ui/DownloadUI.py +++ b/src/ui/DownloadUI.py @@ -1,13 +1,17 @@ +""" +该模块包含DownloadUI类,该类管理下载任务并更新进度条和相关信息 +""" + from __future__ import annotations -from concurrent.futures import ThreadPoolExecutor from typing import TYPE_CHECKING from PySide6.QtCore import QObject, Qt, Signal from PySide6.QtWidgets import QHBoxLayout, QLabel, QProgressBar, QWidget from src.Episode import Episode -from src.Utils import DownloadInfo, openFolderAndSelectItems +from src.Utils import openFolderAndSelectItems +from src.DownloadManager import DownloadManager if TYPE_CHECKING: from src.ui.MainGUI import MainGUI @@ -22,10 +26,14 @@ class DownloadUI(QObject): def __init__(self, mainGUI: MainGUI): super().__init__() - self.id_count = 0 - self.all_tasks = {} - self.download_info = DownloadInfo() - self.executor = ThreadPoolExecutor(max_workers=mainGUI.getConfig("num_thread")) + + self.tasks_info = {} + self.downloadManager = DownloadManager( + max_workers=mainGUI.getConfig("num_thread"), + save_method=mainGUI.getConfig("save_method"), + signal_rate_progress=self.signal_rate_progress, + signal_message_box=mainGUI.signal_message_box, + ) self.init_DownloadUI(mainGUI) @@ -42,46 +50,46 @@ def init_DownloadUI(self, mainGUI: MainGUI) -> None: # ?########################################################### # ? 任务进度更新的信号槽绑定 def _(result: dict): - curr_task = self.all_tasks[result["taskID"]] - curr_task["rate"] = result["rate"] - curr_task["bar"].setValue(result["rate"]) - self.download_info.updateTask(result["taskID"], result["rate"]) + taskID = result["taskID"] + rate = result["rate"] + + # ? 更新当前任务的进度条 + self.tasks_info[taskID]["bar"].setValue(rate) # ? 在下载列表UI里删除下载完成的任务 - # ? 如果result['rate'] 等于1 意味着下载出错跳过,删除任务相关信息 - if result["rate"] in (100, -1): + # ? 如果 rate 等于1 意味着下载出错跳过,删除任务相关信息 + if rate in (-1, 100): for i in reversed(range(mainGUI.verticalLayout_processing.count())): to_delete = mainGUI.verticalLayout_processing.itemAt(i).widget() # ? 如果widget的ObjectName和当前任务的id一致 - if to_delete.objectName() == result["taskID"]: - if result["rate"] == 100: + if to_delete.objectName() == str(taskID): + if rate == 100: # ? 取出标题组件用于添加到已完成列表 label_title = to_delete.layout().itemAt(0).widget() - self.addFinished(mainGUI, label_title, result["path"]) + self.addFinished( + mainGUI, label_title, self.tasks_info[taskID]["path"] + ) # ? deleteLater 会有延迟,为了显示效果,先将父控件设为None to_delete.setParent(None) to_delete.deleteLater() - # ? 更新跳过章节任务相关信息 - if result["rate"] == -1: - self.download_info.updateTask(result["taskID"], 100) - curr_task["rate"] = 100 + + # ? 删除任务字典中的条目 + if rate == -1: + del self.tasks_info[taskID] # ? 更新总进度条的进度,速度和剩余时间 - total_progress = sum( - task[1]["rate"] for task in self.all_tasks.items() - ) / len(self.all_tasks) + total_progress = self.downloadManager.getTotalRate() mainGUI.progressBar_total_progress.setValue(total_progress) if total_progress != 100: mainGUI.label_total_progress_speed.setText( - f"{self.download_info.getTotalSmoothSpeedStr()}" + f"{self.downloadManager.getTotalSpeedStr()}" ) mainGUI.label_total_progress_time.setText( - f"{self.download_info.getTotalRemainingTimeStr()}" + f"{self.downloadManager.getTotalRemainedTimeStr()}" ) else: # ? 100% 后删除所有任务字典中的条目 - self.download_info.removeAllTasks() - self.all_tasks.clear() + self.tasks_info.clear() mainGUI.label_total_progress_speed.setText("总下载速度:") mainGUI.label_total_progress_time.setText("剩余时间:") @@ -133,14 +141,7 @@ def addTask(self, mainGUI: MainGUI, epi: Episode) -> None: # ?########################################################### # ? 创建任务 - task_id = str(self.id_count) - self.download_info.createTask(task_id, epi.size) - self.all_tasks[task_id] = { - "rate": 0, - "future": self.executor.submit( - epi.download, self.signal_rate_progress, task_id - ), - } + task_id = self.downloadManager.createEpisodeTask(epi) # ?########################################################### # ? 添加任务组件到正在下载列表 @@ -150,15 +151,18 @@ def addTask(self, mainGUI: MainGUI, epi: Episode) -> None: f"{epi.comic_name} > {epi.title}" ) ) - bar = QProgressBar() - bar.setTextVisible(True) + progress_bar = QProgressBar() + progress_bar.setTextVisible(True) + + self.tasks_info[task_id] = { + "bar": progress_bar, + "path": epi.epi_path, + } - self.all_tasks[task_id]["bar"] = bar - h_layout_download_list.addWidget(bar) + h_layout_download_list.addWidget(progress_bar) h_layout_download_list.setStretch(0, 1) h_layout_download_list.setStretch(1, 1) widget = QWidget() - widget.setObjectName(task_id) + widget.setObjectName(str(task_id)) widget.setLayout(h_layout_download_list) mainGUI.verticalLayout_processing.addWidget(widget) - self.id_count += 1 From a7da374be6a26759c720214bf16eb080ffed996f Mon Sep 17 00:00:00 2001 From: Zeal-L Date: Sun, 20 Aug 2023 20:55:20 +1000 Subject: [PATCH 6/8] =?UTF-8?q?:bug:=20=E4=BC=98=E5=8C=96=E4=B8=8B?= =?UTF-8?q?=E8=BD=BD=E9=80=9F=E5=BA=A6=E5=92=8C=E5=89=A9=E4=BD=99=E6=97=B6?= =?UTF-8?q?=E9=97=B4=E7=9A=84=E8=AE=A1=E7=AE=97=E4=B8=8E=E8=8E=B7=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/BiliPlus.py | 1 - src/DownloadManager.py | 22 +++++++++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/BiliPlus.py b/src/BiliPlus.py index e713fe2..1cf2899 100644 --- a/src/BiliPlus.py +++ b/src/BiliPlus.py @@ -25,7 +25,6 @@ def __init__(self, comic_id: int, mainGUI: MainGUI) -> None: super().__init__(comic_id, mainGUI) self.access_key = mainGUI.getConfig("biliplus_cookie") self.headers = { - "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36", "cookie": f"manga_pic_format=jpg-full;login=2;access_key={self.access_key}", } diff --git a/src/DownloadManager.py b/src/DownloadManager.py index 03448a3..09f1374 100644 --- a/src/DownloadManager.py +++ b/src/DownloadManager.py @@ -27,7 +27,7 @@ def __init__( self.signal_message_box = signal_message_box self.all_tasks = {} - self.avg_speed_in_last_five_sec = {} + self.avg_speed_in_last_three_sec = {} ############################################################ @@ -78,6 +78,10 @@ def updateTaskInfo(self, curr_id: int, rate: float) -> None: def getTotalRate(self) -> float: """获取所有任务的平均下载进度""" + + if len(self.all_tasks) == 0: + return 100.0 + return ( sum(task["curr_rate"] for task in self.all_tasks.values()) / len(self.all_tasks) @@ -92,19 +96,19 @@ def getTotalSpeed(self) -> float: Returns: float: 平均下载速度 """ - self.avg_speed_in_last_five_sec[time.perf_counter()] = sum( + self.avg_speed_in_last_three_sec[time.perf_counter()] = sum( task["curr_speed"] for task in self.all_tasks.values() ) - # 取5秒内的平均速度,以防止速度突然变化 + # 取3秒内的平均速度,以防止速度突然变化 # 比如下载完一个文件 速度突然变为0 # 或者开始一组新的下载,速度突然变为很大 - for key in list(self.avg_speed_in_last_five_sec.keys()): - if key < time.perf_counter() - 5: - self.avg_speed_in_last_five_sec.pop(key) + for key in list(self.avg_speed_in_last_three_sec.keys()): + if key < time.perf_counter() - 3: + self.avg_speed_in_last_three_sec.pop(key) - return sum(self.avg_speed_in_last_five_sec.values()) / len( - self.avg_speed_in_last_five_sec + return sum(self.avg_speed_in_last_three_sec.values()) / len( + self.avg_speed_in_last_three_sec ) ############################################################ @@ -167,6 +171,7 @@ def __thread__EpisodeTask(self, curr_id: int, epi: Episode) -> None: "rate": int(rate * 100), } ) + self.clearAfterFinish(curr_id) # ?########################################################### # ? 保存图片 @@ -180,7 +185,6 @@ def __thread__EpisodeTask(self, curr_id: int, epi: Episode) -> None: epi.clearAfterSave(imgs_path) - self.clearAfterFinish(curr_id) ############################################################ From 8a0169e294af4d9c0d17921aa5d50f8345568204 Mon Sep 17 00:00:00 2001 From: Zeal-L Date: Fri, 25 Aug 2023 19:04:55 +1000 Subject: [PATCH 7/8] =?UTF-8?q?:bug:=20=E4=BF=AE=E5=A4=8Dbug:=20=E4=BF=9D?= =?UTF-8?q?=E5=AD=98=E6=A0=BC=E5=BC=8F=E4=B8=BA=20`pdf`=20=E6=97=B6?= =?UTF-8?q?=E5=AF=BC=E8=87=B4=E5=86=85=E5=AD=98=E6=B3=84=E6=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 +- Pipfile.lock | 1208 ++++++++++++++++++++++++++++++ README.md | 40 +- UPDATE.md | 8 + linting.py | 21 - src/Comic.py | 4 +- src/DownloadManager.py | 36 +- src/Episode.py | 4 + src/Utils.py | 4 +- src/ui/MainGUI.py | 1 + src/ui/PySide_src/myAbout.ui | 2 +- src/ui/PySide_src/myAbout_ui.py | 2 +- src/ui/PySide_src/resource_rc.py | 2 +- version.txt | 10 +- 14 files changed, 1276 insertions(+), 68 deletions(-) create mode 100644 Pipfile.lock delete mode 100644 linting.py diff --git a/.gitignore b/.gitignore index 263d060..3199b4d 100644 --- a/.gitignore +++ b/.gitignore @@ -98,7 +98,7 @@ ipython_config.py # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. -Pipfile.lock +# Pipfile.lock # celery beat schedule file celerybeat-schedule diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..1e757f8 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,1208 @@ +{ + "_meta": { + "hash": { + "sha256": "d2a9c21ac67e6c95123fa1995a9243b534a5fd07baa380b22e20ac41f014b1c3" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.11" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "beautifulsoup4": { + "hashes": [ + "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da", + "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a" + ], + "index": "pypi", + "version": "==4.12.2" + }, + "brotli": { + "hashes": [ + "sha256:02177603aaca36e1fd21b091cb742bb3b305a569e2402f1ca38af471777fb019", + "sha256:11d3283d89af7033236fa4e73ec2cbe743d4f6a81d41bd234f24bf63dde979df", + "sha256:12effe280b8ebfd389022aa65114e30407540ccb89b177d3fbc9a4f177c4bd5d", + "sha256:160c78292e98d21e73a4cc7f76a234390e516afcd982fa17e1422f7c6a9ce9c8", + "sha256:16d528a45c2e1909c2798f27f7bf0a3feec1dc9e50948e738b961618e38b6a7b", + "sha256:19598ecddd8a212aedb1ffa15763dd52a388518c4550e615aed88dc3753c0f0c", + "sha256:1c48472a6ba3b113452355b9af0a60da5c2ae60477f8feda8346f8fd48e3e87c", + "sha256:268fe94547ba25b58ebc724680609c8ee3e5a843202e9a381f6f9c5e8bdb5c70", + "sha256:269a5743a393c65db46a7bb982644c67ecba4b8d91b392403ad8a861ba6f495f", + "sha256:26d168aac4aaec9a4394221240e8a5436b5634adc3cd1cdf637f6645cecbf181", + "sha256:29d1d350178e5225397e28ea1b7aca3648fcbab546d20e7475805437bfb0a130", + "sha256:2aad0e0baa04517741c9bb5b07586c642302e5fb3e75319cb62087bd0995ab19", + "sha256:3148362937217b7072cf80a2dcc007f09bb5ecb96dae4617316638194113d5be", + "sha256:330e3f10cd01da535c70d09c4283ba2df5fb78e915bea0a28becad6e2ac010be", + "sha256:336b40348269f9b91268378de5ff44dc6fbaa2268194f85177b53463d313842a", + "sha256:3496fc835370da351d37cada4cf744039616a6db7d13c430035e901443a34daa", + "sha256:35a3edbe18e876e596553c4007a087f8bcfd538f19bc116917b3c7522fca0429", + "sha256:3b78a24b5fd13c03ee2b7b86290ed20efdc95da75a3557cc06811764d5ad1126", + "sha256:3b8b09a16a1950b9ef495a0f8b9d0a87599a9d1f179e2d4ac014b2ec831f87e7", + "sha256:3c1306004d49b84bd0c4f90457c6f57ad109f5cc6067a9664e12b7b79a9948ad", + "sha256:3ffaadcaeafe9d30a7e4e1e97ad727e4f5610b9fa2f7551998471e3736738679", + "sha256:40d15c79f42e0a2c72892bf407979febd9cf91f36f495ffb333d1d04cebb34e4", + "sha256:44bb8ff420c1d19d91d79d8c3574b8954288bdff0273bf788954064d260d7ab0", + "sha256:4688c1e42968ba52e57d8670ad2306fe92e0169c6f3af0089be75bbac0c64a3b", + "sha256:495ba7e49c2db22b046a53b469bbecea802efce200dffb69b93dd47397edc9b6", + "sha256:4d1b810aa0ed773f81dceda2cc7b403d01057458730e309856356d4ef4188438", + "sha256:503fa6af7da9f4b5780bb7e4cbe0c639b010f12be85d02c99452825dd0feef3f", + "sha256:56d027eace784738457437df7331965473f2c0da2c70e1a1f6fdbae5402e0389", + "sha256:5913a1177fc36e30fcf6dc868ce23b0453952c78c04c266d3149b3d39e1410d6", + "sha256:5b6ef7d9f9c38292df3690fe3e302b5b530999fa90014853dcd0d6902fb59f26", + "sha256:5bf37a08493232fbb0f8229f1824b366c2fc1d02d64e7e918af40acd15f3e337", + "sha256:5cb1e18167792d7d21e21365d7650b72d5081ed476123ff7b8cac7f45189c0c7", + "sha256:61a7ee1f13ab913897dac7da44a73c6d44d48a4adff42a5701e3239791c96e14", + "sha256:622a231b08899c864eb87e85f81c75e7b9ce05b001e59bbfbf43d4a71f5f32b2", + "sha256:68715970f16b6e92c574c30747c95cf8cf62804569647386ff032195dc89a430", + "sha256:6b2ae9f5f67f89aade1fab0f7fd8f2832501311c363a21579d02defa844d9296", + "sha256:6c772d6c0a79ac0f414a9f8947cc407e119b8598de7621f39cacadae3cf57d12", + "sha256:6d847b14f7ea89f6ad3c9e3901d1bc4835f6b390a9c71df999b0162d9bb1e20f", + "sha256:73fd30d4ce0ea48010564ccee1a26bfe39323fde05cb34b5863455629db61dc7", + "sha256:76ffebb907bec09ff511bb3acc077695e2c32bc2142819491579a695f77ffd4d", + "sha256:7bbff90b63328013e1e8cb50650ae0b9bac54ffb4be6104378490193cd60f85a", + "sha256:7cb81373984cc0e4682f31bc3d6be9026006d96eecd07ea49aafb06897746452", + "sha256:7ee83d3e3a024a9618e5be64648d6d11c37047ac48adff25f12fa4226cf23d1c", + "sha256:854c33dad5ba0fbd6ab69185fec8dab89e13cda6b7d191ba111987df74f38761", + "sha256:85f7912459c67eaab2fb854ed2bc1cc25772b300545fe7ed2dc03954da638649", + "sha256:87fdccbb6bb589095f413b1e05734ba492c962b4a45a13ff3408fa44ffe6479b", + "sha256:88c63a1b55f352b02c6ffd24b15ead9fc0e8bf781dbe070213039324922a2eea", + "sha256:8a674ac10e0a87b683f4fa2b6fa41090edfd686a6524bd8dedbd6138b309175c", + "sha256:8ed6a5b3d23ecc00ea02e1ed8e0ff9a08f4fc87a1f58a2530e71c0f48adf882f", + "sha256:93130612b837103e15ac3f9cbacb4613f9e348b58b3aad53721d92e57f96d46a", + "sha256:9744a863b489c79a73aba014df554b0e7a0fc44ef3f8a0ef2a52919c7d155031", + "sha256:9749a124280a0ada4187a6cfd1ffd35c350fb3af79c706589d98e088c5044267", + "sha256:97f715cf371b16ac88b8c19da00029804e20e25f30d80203417255d239f228b5", + "sha256:9bf919756d25e4114ace16a8ce91eb340eb57a08e2c6950c3cebcbe3dff2a5e7", + "sha256:9d12cf2851759b8de8ca5fde36a59c08210a97ffca0eb94c532ce7b17c6a3d1d", + "sha256:9ed4c92a0665002ff8ea852353aeb60d9141eb04109e88928026d3c8a9e5433c", + "sha256:a72661af47119a80d82fa583b554095308d6a4c356b2a554fdc2799bc19f2a43", + "sha256:afde17ae04d90fbe53afb628f7f2d4ca022797aa093e809de5c3cf276f61bbfa", + "sha256:b1375b5d17d6145c798661b67e4ae9d5496920d9265e2f00f1c2c0b5ae91fbde", + "sha256:b336c5e9cf03c7be40c47b5fd694c43c9f1358a80ba384a21969e0b4e66a9b17", + "sha256:b3523f51818e8f16599613edddb1ff924eeb4b53ab7e7197f85cbc321cdca32f", + "sha256:b43775532a5904bc938f9c15b77c613cb6ad6fb30990f3b0afaea82797a402d8", + "sha256:b663f1e02de5d0573610756398e44c130add0eb9a3fc912a09665332942a2efb", + "sha256:b83bb06a0192cccf1eb8d0a28672a1b79c74c3a8a5f2619625aeb6f28b3a82bb", + "sha256:ba72d37e2a924717990f4d7482e8ac88e2ef43fb95491eb6e0d124d77d2a150d", + "sha256:c2415d9d082152460f2bd4e382a1e85aed233abc92db5a3880da2257dc7daf7b", + "sha256:c83aa123d56f2e060644427a882a36b3c12db93727ad7a7b9efd7d7f3e9cc2c4", + "sha256:c8e521a0ce7cf690ca84b8cc2272ddaf9d8a50294fd086da67e517439614c755", + "sha256:cab1b5964b39607a66adbba01f1c12df2e55ac36c81ec6ed44f2fca44178bf1a", + "sha256:cb02ed34557afde2d2da68194d12f5719ee96cfb2eacc886352cb73e3808fc5d", + "sha256:cc0283a406774f465fb45ec7efb66857c09ffefbe49ec20b7882eff6d3c86d3a", + "sha256:cfc391f4429ee0a9370aa93d812a52e1fee0f37a81861f4fdd1f4fb28e8547c3", + "sha256:db844eb158a87ccab83e868a762ea8024ae27337fc7ddcbfcddd157f841fdfe7", + "sha256:defed7ea5f218a9f2336301e6fd379f55c655bea65ba2476346340a0ce6f74a1", + "sha256:e16eb9541f3dd1a3e92b89005e37b1257b157b7256df0e36bd7b33b50be73bcb", + "sha256:e1abbeef02962596548382e393f56e4c94acd286bd0c5afba756cffc33670e8a", + "sha256:e23281b9a08ec338469268f98f194658abfb13658ee98e2b7f85ee9dd06caa91", + "sha256:e2d9e1cbc1b25e22000328702b014227737756f4b5bf5c485ac1d8091ada078b", + "sha256:e48f4234f2469ed012a98f4b7874e7f7e173c167bed4934912a29e03167cf6b1", + "sha256:e4c4e92c14a57c9bd4cb4be678c25369bf7a092d55fd0866f759e425b9660806", + "sha256:ec1947eabbaf8e0531e8e899fc1d9876c179fc518989461f5d24e2223395a9e3", + "sha256:f909bbbc433048b499cb9db9e713b5d8d949e8c109a2a548502fb9aa8630f0b1" + ], + "markers": "platform_python_implementation == 'CPython'", + "version": "==1.0.9" + }, + "certifi": { + "hashes": [ + "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082", + "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9" + ], + "markers": "python_version >= '3.6'", + "version": "==2023.7.22" + }, + "charset-normalizer": { + "hashes": [ + "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96", + "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c", + "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710", + "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706", + "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020", + "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252", + "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad", + "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329", + "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a", + "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f", + "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6", + "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4", + "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a", + "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46", + "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2", + "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23", + "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace", + "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd", + "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982", + "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10", + "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2", + "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea", + "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09", + "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5", + "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149", + "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489", + "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9", + "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80", + "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592", + "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3", + "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6", + "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed", + "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c", + "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200", + "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a", + "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e", + "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d", + "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6", + "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623", + "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669", + "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3", + "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa", + "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9", + "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2", + "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f", + "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1", + "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4", + "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a", + "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8", + "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3", + "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029", + "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f", + "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959", + "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22", + "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7", + "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952", + "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346", + "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e", + "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d", + "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299", + "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd", + "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a", + "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3", + "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037", + "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94", + "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c", + "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858", + "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a", + "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449", + "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c", + "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918", + "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1", + "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c", + "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac", + "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa" + ], + "markers": "python_full_version >= '3.7.0'", + "version": "==3.2.0" + }, + "colorama": { + "hashes": [ + "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", + "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" + ], + "markers": "platform_system == 'Windows'", + "version": "==0.4.6" + }, + "idna": { + "hashes": [ + "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", + "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" + ], + "markers": "python_version >= '3.5'", + "version": "==3.4" + }, + "inflate64": { + "hashes": [ + "sha256:08c3b03514d4b849901762a32a45eeba7fd5d784fec698eca6975f41cca33672", + "sha256:094ef56a87c7b7398d93af7bfe7f24f830f24b6e55b77426f6516cef43e05460", + "sha256:09dd0f8d6dee0da467c264dbd9bca8b33f9c915860fc3385f2a633640a65bd10", + "sha256:0b0c8aa2fcdb1052d3bc6c5b5b1191b9c708d30e47af98ba0a8117ae1f6c9efc", + "sha256:130dfdca4bd38e588ea4f878bf62635e36f83ddf7f2842d1055d1c16a11890cf", + "sha256:1749da3a02b53035cde1cf95f885e78e0c2c49b201e97d368b3ba97e0f3d42c3", + "sha256:17aaac096f40bd80dd72481831607a0846271d401ba3cd863386b8c244c7ebc1", + "sha256:1d861fed6b2098d1862b64db9df650b9bd41fc41caa9fcaeee399079342aa4a8", + "sha256:26e8319fd032c520203e2c001f1693c1c03774d85915900427e884011718f41d", + "sha256:29946840e6970d68e7739207ca21140c59ffebe7e02d28c7e86348166ce32418", + "sha256:35e24ffd8d6225fbfe26c524b45ace1bb8956811bd79e9f3d523a721d51b0d4e", + "sha256:3a17e1dd1a5a872edfc02bc4a048868ada4865a3f4ee3ad5d224b192f2e53df7", + "sha256:3bacbe9d4b7c185011b59268223a010ed777a28ed8cf40efc74fab1b7262e904", + "sha256:3c270d373ca3717dbeb9b171eea53cbf2c9d7471b9b5de1e57f165e60cf58037", + "sha256:3c913b679f023f5907a54bfa9a6e438407ed4e40eee23ed19b4118128bdd091c", + "sha256:3cf41f82dd4e90e8684c7be4583d7232bd800a561f3ed0241c84e39148861887", + "sha256:41504988023042452d2d84e4110c9ef4ff8ebd33cb90ba83e44b92c9a6753c43", + "sha256:42a6ef375b3e7059bd52993a0938f2bf97725cb5dc380f0c4dbaa9fc3780e025", + "sha256:473e0081c268ffa4b18683586b55170eb96d8b4fc684dd3ed9599c17c512d2e4", + "sha256:48fd2527a462374dc19be06301d6aa30a03190532f2f8bddfbc39b7158561750", + "sha256:4d807cfa9ddad940401ef04502eb367a77f569850f59c2e71670347d558a3830", + "sha256:4e7b0a598adaa11366ffbbb7b3d3110db29edd4b732d9336570891363b22b002", + "sha256:525bc309d8533ef9917e006284996ee7a9a71ac6dd19fb57c0f741ad0c805d4f", + "sha256:553cd992f02af574d2116c74ca48d7cf10894c6b9ba8159f488f3bfac3c201ae", + "sha256:5c5b2eb7e89d550d287774dea7d429ee24ce44ca34499a6cef113a14f108e700", + "sha256:5efd55c21b794601fd44b99b8e2f17498744f573116ce27a745bc5e08f0457e1", + "sha256:6059eaba5044739ad6424588e845bd856f89a1a18f1addc31b97c49f02f68728", + "sha256:664929528047b6b472852a4c0d12b4b9cf6e663059ba64ebd10f08aa56365755", + "sha256:67e37d96ea2ee8257b12cde83a09e4f0276950268a7a2f777aee7de60db5ec72", + "sha256:67efdfd21d7b99f30a43560b22264c1e580ff08ae9831e78c99445575962dbc7", + "sha256:6ff89f94823b2466bae45759fc324bd25bd20c490607a7d8407237cf64ccafa9", + "sha256:74ceb9d172ce06572632bc8070d54b963455421e216013575383f991e722bb7d", + "sha256:7b7966193f1bf23e050af72b4c4720dffa5f33471de7afea37ba0d0f0195adef", + "sha256:7ba954600441eafe8f6f54eadffeac4d1ab2416d5d1a6b0ab403e50284ba457b", + "sha256:7c142fbbbfbe0877fe821ff8bc4cc10f96d344b7400721579b3d17deeae28f59", + "sha256:7f8346e644de449a4a90dcb22971dea456398b6cc788102013675b11256ae47e", + "sha256:80a125dd5cb7b7985c05a78b0bfd7751249d0d84fc330901dbd9faa693e1f53f", + "sha256:82393e46b8ba2f8613d030f38c7c492b0896ff8803f7ff870677f25d3e5e7113", + "sha256:84287d1d09fd879353d3ccadd43f3d8adea75e830476ddfd46d8849d36d25afe", + "sha256:853f3442eceda8035072686533694ab833c4293d10c9d0685147200f0e964356", + "sha256:906a4b57df32f903e847766ca685e44ed3e7ee3a960fa94264d5e68b836d446d", + "sha256:90f95b92d0f672d11151cb964964d1723e2e3ce3a19d32d24aece1acdec1e287", + "sha256:91233b5300bbb7562804c3d07617e9ce2983e8434218991db98ef175491e417f", + "sha256:9297115bf144c585e9d6a746e851c64c81d8f1ce8b62da4885babe66c36f7d29", + "sha256:9f6737a575c6e7e818963d95a998be4c91484374961734cee97265f3c4c3b979", + "sha256:a075b174bace5174828906c7c87019a2af3cc5707025f01ee0395fb4b88fd98e", + "sha256:a1b481343f12641b1ae7a19135a70c44ecf020dccb670e02522c2b02db920851", + "sha256:a2f4aaa02f9a5ada944960428b6528a0a9d773925efc73485882f34bf42654be", + "sha256:a6bec3d2f30f6f2656e1c5a4147181e401c8d7026cd598d86ad5647c616fc618", + "sha256:aa7476129e7f81e67a9253470c3085a9fd75ec77e6fae3de61f7795138ce725e", + "sha256:ab8f9e14ba6495f440101751ba8aa371e4a52941b5e343c6f3e8c61021e2df5e", + "sha256:ac60868745f7bfbcd615329fbdc35997fa36043ce358a1c64d229ef448ebecf0", + "sha256:ad4cae5097bdff7e0bb1ab676d86ad08716597baa3b616e5b710a724f5d5cbc4", + "sha256:ad84ac611eae17a961124c5fbe754b6982291a3945ab2b9c334a08e2e56f9ccc", + "sha256:b52dd8fefd2ba179e5dfa18d6eca7e2fc822584616271c039d5ef1f9ca90c71c", + "sha256:b7aa123c740f2f9798f72873e50d7c6d43664d12cad7a1405296079987bdb04a", + "sha256:c1faf43890dbfff31195f5d59e37e49824f5ff4be77d67f7144a6b953bbde51c", + "sha256:c28cb635ccb9aae399fbc8e82c85b89ea0a7bb2219e7d582bbc007a29fb6e149", + "sha256:c71821f93c931ae379cf9c9bbdd7099738fa00802ccf2a5271e2b68bc67a6ab8", + "sha256:ced0af509a31dcba0cd98ecdd06cb7c9ce66ebde78e0d99ba3515d4e991e34d0", + "sha256:d4e2a337c6c03b0e96ccd79940cbb04fe2063974d56fff6d78f8d57839546c57", + "sha256:d71af8b23ac23bc9e9f776451c125be6320ad4589a7d5bcb5ab5e1fc61b4e58f", + "sha256:d881b605b7448be451f02c59128dc5fac262dbd0dcff4638e702dc8c7bbb8ef0", + "sha256:e1987bbc482aa3e2e7fb72c70b22483cfaed3dbebc5ba6f9ac6f75240794709b", + "sha256:e32a78c81afba5699569c3493066ecb38fb45ccdf4c35b3c2232c9c2585b5257", + "sha256:f2a4dac4ebc4ad58a4ac911e39cf97cd74906c0c82c16333887aa9f287e98d5b", + "sha256:f39b57974db0e85897fff40518da420f4c4012b73515ca6f415a472228fea288", + "sha256:fd04764d0bb830414788cae897d082bf6ad92324e571a5511bd7e1de4a0cdc67", + "sha256:fde3f85864c84badb26f42d95639360e627fd09c529a76c46a06dbd7a5735c51" + ], + "markers": "python_version >= '3.7'", + "version": "==0.3.1" + }, + "jinja2": { + "hashes": [ + "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852", + "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61" + ], + "markers": "python_version >= '3.7'", + "version": "==3.1.2" + }, + "markupsafe": { + "hashes": [ + "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e", + "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e", + "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431", + "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686", + "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559", + "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc", + "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c", + "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0", + "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4", + "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9", + "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575", + "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba", + "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d", + "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3", + "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00", + "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155", + "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac", + "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52", + "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f", + "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8", + "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b", + "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24", + "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea", + "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198", + "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0", + "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee", + "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be", + "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2", + "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707", + "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6", + "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58", + "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779", + "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636", + "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c", + "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad", + "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee", + "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc", + "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2", + "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48", + "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7", + "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e", + "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b", + "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa", + "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5", + "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e", + "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb", + "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9", + "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57", + "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc", + "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2" + ], + "markers": "python_version >= '3.7'", + "version": "==2.1.3" + }, + "multivolumefile": { + "hashes": [ + "sha256:237f4353b60af1703087cf7725755a1f6fcaeeea48421e1896940cd1c920d678", + "sha256:a0648d0aafbc96e59198d5c17e9acad7eb531abea51035d08ce8060dcad709d6" + ], + "markers": "python_version >= '3.6'", + "version": "==0.2.3" + }, + "piexif": { + "hashes": [ + "sha256:3bc435d171720150b81b15d27e05e54b8abbde7b4242cddd81ef160d283108b6", + "sha256:83cb35c606bf3a1ea1a8f0a25cb42cf17e24353fd82e87ae3884e74a302a5f1b" + ], + "index": "pypi", + "version": "==1.1.3" + }, + "pillow": { + "hashes": [ + "sha256:00e65f5e822decd501e374b0650146063fbb30a7264b4d2744bdd7b913e0cab5", + "sha256:040586f7d37b34547153fa383f7f9aed68b738992380ac911447bb78f2abe530", + "sha256:0b6eb5502f45a60a3f411c63187db83a3d3107887ad0d036c13ce836f8a36f1d", + "sha256:1ce91b6ec08d866b14413d3f0bbdea7e24dfdc8e59f562bb77bc3fe60b6144ca", + "sha256:1f62406a884ae75fb2f818694469519fb685cc7eaff05d3451a9ebe55c646891", + "sha256:22c10cc517668d44b211717fd9775799ccec4124b9a7f7b3635fc5386e584992", + "sha256:3400aae60685b06bb96f99a21e1ada7bc7a413d5f49bce739828ecd9391bb8f7", + "sha256:349930d6e9c685c089284b013478d6f76e3a534e36ddfa912cde493f235372f3", + "sha256:368ab3dfb5f49e312231b6f27b8820c823652b7cd29cfbd34090565a015e99ba", + "sha256:38250a349b6b390ee6047a62c086d3817ac69022c127f8a5dc058c31ccef17f3", + "sha256:3a684105f7c32488f7153905a4e3015a3b6c7182e106fe3c37fbb5ef3e6994c3", + "sha256:3a82c40d706d9aa9734289740ce26460a11aeec2d9c79b7af87bb35f0073c12f", + "sha256:3b08d4cc24f471b2c8ca24ec060abf4bebc6b144cb89cba638c720546b1cf538", + "sha256:3ed64f9ca2f0a95411e88a4efbd7a29e5ce2cea36072c53dd9d26d9c76f753b3", + "sha256:3f07ea8d2f827d7d2a49ecf1639ec02d75ffd1b88dcc5b3a61bbb37a8759ad8d", + "sha256:520f2a520dc040512699f20fa1c363eed506e94248d71f85412b625026f6142c", + "sha256:5c6e3df6bdd396749bafd45314871b3d0af81ff935b2d188385e970052091017", + "sha256:608bfdee0d57cf297d32bcbb3c728dc1da0907519d1784962c5f0c68bb93e5a3", + "sha256:685ac03cc4ed5ebc15ad5c23bc555d68a87777586d970c2c3e216619a5476223", + "sha256:76de421f9c326da8f43d690110f0e79fe3ad1e54be811545d7d91898b4c8493e", + "sha256:76edb0a1fa2b4745fb0c99fb9fb98f8b180a1bbceb8be49b087e0b21867e77d3", + "sha256:7be600823e4c8631b74e4a0d38384c73f680e6105a7d3c6824fcf226c178c7e6", + "sha256:81ff539a12457809666fef6624684c008e00ff6bf455b4b89fd00a140eecd640", + "sha256:88af2003543cc40c80f6fca01411892ec52b11021b3dc22ec3bc9d5afd1c5334", + "sha256:8c11160913e3dd06c8ffdb5f233a4f254cb449f4dfc0f8f4549eda9e542c93d1", + "sha256:8f8182b523b2289f7c415f589118228d30ac8c355baa2f3194ced084dac2dbba", + "sha256:9211e7ad69d7c9401cfc0e23d49b69ca65ddd898976d660a2fa5904e3d7a9baa", + "sha256:92be919bbc9f7d09f7ae343c38f5bb21c973d2576c1d45600fce4b74bafa7ac0", + "sha256:9c82b5b3e043c7af0d95792d0d20ccf68f61a1fec6b3530e718b688422727396", + "sha256:9f7c16705f44e0504a3a2a14197c1f0b32a95731d251777dcb060aa83022cb2d", + "sha256:9fb218c8a12e51d7ead2a7c9e101a04982237d4855716af2e9499306728fb485", + "sha256:a74ba0c356aaa3bb8e3eb79606a87669e7ec6444be352870623025d75a14a2bf", + "sha256:b4f69b3700201b80bb82c3a97d5e9254084f6dd5fb5b16fc1a7b974260f89f43", + "sha256:bc2ec7c7b5d66b8ec9ce9f720dbb5fa4bace0f545acd34870eff4a369b44bf37", + "sha256:c189af0545965fa8d3b9613cfdb0cd37f9d71349e0f7750e1fd704648d475ed2", + "sha256:c1fbe7621c167ecaa38ad29643d77a9ce7311583761abf7836e1510c580bf3dd", + "sha256:c7cf14a27b0d6adfaebb3ae4153f1e516df54e47e42dcc073d7b3d76111a8d86", + "sha256:c9f72a021fbb792ce98306ffb0c348b3c9cb967dce0f12a49aa4c3d3fdefa967", + "sha256:cd25d2a9d2b36fcb318882481367956d2cf91329f6892fe5d385c346c0649629", + "sha256:ce543ed15570eedbb85df19b0a1a7314a9c8141a36ce089c0a894adbfccb4568", + "sha256:ce7b031a6fc11365970e6a5686d7ba8c63e4c1cf1ea143811acbb524295eabed", + "sha256:d35e3c8d9b1268cbf5d3670285feb3528f6680420eafe35cccc686b73c1e330f", + "sha256:d50b6aec14bc737742ca96e85d6d0a5f9bfbded018264b3b70ff9d8c33485551", + "sha256:d5d0dae4cfd56969d23d94dc8e89fb6a217be461c69090768227beb8ed28c0a3", + "sha256:d5db32e2a6ccbb3d34d87c87b432959e0db29755727afb37290e10f6e8e62614", + "sha256:d72e2ecc68a942e8cf9739619b7f408cc7b272b279b56b2c83c6123fcfa5cdff", + "sha256:d737a602fbd82afd892ca746392401b634e278cb65d55c4b7a8f48e9ef8d008d", + "sha256:d80cf684b541685fccdd84c485b31ce73fc5c9b5d7523bf1394ce134a60c6883", + "sha256:db24668940f82321e746773a4bc617bfac06ec831e5c88b643f91f122a785684", + "sha256:dbc02381779d412145331789b40cc7b11fdf449e5d94f6bc0b080db0a56ea3f0", + "sha256:dffe31a7f47b603318c609f378ebcd57f1554a3a6a8effbc59c3c69f804296de", + "sha256:edf4392b77bdc81f36e92d3a07a5cd072f90253197f4a52a55a8cec48a12483b", + "sha256:efe8c0681042536e0d06c11f48cebe759707c9e9abf880ee213541c5b46c5bf3", + "sha256:f31f9fdbfecb042d046f9d91270a0ba28368a723302786c0009ee9b9f1f60199", + "sha256:f88a0b92277de8e3ca715a0d79d68dc82807457dae3ab8699c758f07c20b3c51", + "sha256:faaf07ea35355b01a35cb442dd950d8f1bb5b040a7787791a535de13db15ed90" + ], + "index": "pypi", + "version": "==10.0.0" + }, + "psutil": { + "hashes": [ + "sha256:104a5cc0e31baa2bcf67900be36acde157756b9c44017b86b2c049f11957887d", + "sha256:3c6f686f4225553615612f6d9bc21f1c0e305f75d7d8454f9b46e901778e7217", + "sha256:4aef137f3345082a3d3232187aeb4ac4ef959ba3d7c10c33dd73763fbc063da4", + "sha256:5410638e4df39c54d957fc51ce03048acd8e6d60abc0f5107af51e5fb566eb3c", + "sha256:5b9b8cb93f507e8dbaf22af6a2fd0ccbe8244bf30b1baad6b3954e935157ae3f", + "sha256:7a7dd9997128a0d928ed4fb2c2d57e5102bb6089027939f3b722f3a210f9a8da", + "sha256:89518112647f1276b03ca97b65cc7f64ca587b1eb0278383017c2a0dcc26cbe4", + "sha256:8c5f7c5a052d1d567db4ddd231a9d27a74e8e4a9c3f44b1032762bd7b9fdcd42", + "sha256:ab8ed1a1d77c95453db1ae00a3f9c50227ebd955437bcf2a574ba8adbf6a74d5", + "sha256:acf2aef9391710afded549ff602b5887d7a2349831ae4c26be7c807c0a39fac4", + "sha256:b258c0c1c9d145a1d5ceffab1134441c4c5113b2417fafff7315a917a026c3c9", + "sha256:be8929ce4313f9f8146caad4272f6abb8bf99fc6cf59344a3167ecd74f4f203f", + "sha256:c607bb3b57dc779d55e1554846352b4e358c10fff3abf3514a7a6601beebdb30", + "sha256:ea8518d152174e1249c4f2a1c89e3e6065941df2fa13a1ab45327716a23c2b48" + ], + "markers": "sys_platform != 'cygwin'", + "version": "==5.9.5" + }, + "py7zr": { + "hashes": [ + "sha256:c7cfb7183fb8f48038f1036a116ca89dc8bd57979d05b75567f00e88a5afe698", + "sha256:d036dee11fce69ad8d4fa86025ccfc4a3511ec27ee1c6b5bd8d6759313dbd077" + ], + "index": "pypi", + "version": "==0.20.6" + }, + "pybcj": { + "hashes": [ + "sha256:023082fd677f67ebd36fe96322a4a45ac33a2b340d49010d88e1867c76744c50", + "sha256:05fad9a905772774aacc96cb174571ac1f5afa80b9f54c6ec414d369865d305c", + "sha256:09872b32edad4e3653d5b357b244d267ca58fe52d4e1dd3cdff816d3bb9d9f7c", + "sha256:0eaa90639992b6096afb1485380fae7f084483db6b92867847a3bfdf22cc4efc", + "sha256:10182725b0e6aa944d13a10a4a9cb5208bafe0016b4326253340948153de4bc0", + "sha256:10961ea10ae930b9348132707b9dd3cf3e71a41ef1df7656fbc4f14a71f10747", + "sha256:15edd1786617127ecfda4274bbb04f09ae299c474ada86e369bcf050d5cb88dd", + "sha256:1a5365edcaa82dc47e7757ba2efb48f96b9b352e3811a2aaa90084802479ddbe", + "sha256:1c0e1657c233f9f4070ab578951e03d569f1b645042ce661341091f50e41b541", + "sha256:1e6434a46f852cd3e6929633b43537887bd381bc614dbf5c4a128fdde4966b3a", + "sha256:20fc0d8f67e2d9747e0c31082d5f64b112258ae602a85aa5c7e6bf5a7cad287b", + "sha256:21098001200273c3c9fd90e7bf909fb905a8e1c102c80b604cb7c6a3103ef7e0", + "sha256:225a0addf4b3d580bf4eae583b5168dac0125a703c53ded8b3f120882e1e0312", + "sha256:262f53e27bca6096e3424c63e5e59948b10985eee4b03a5d70c3f3f6161a79e7", + "sha256:2d6b34ec233fcf5a83ccfbf422fef22256947eaa7077aaa012e5961d15aa302c", + "sha256:2e1859d36c073231737956fbeb5bbcfa8dba880e1b66bfbd001466718d6d89dc", + "sha256:358dba3dc39a07cded6897b9f99bb5b951a0ad95d567eda535b44861caa02f5b", + "sha256:3847387b43af47d9677952b8a22d9c2d8a544c2175b6d5304c200669c05d39e1", + "sha256:393d95f83e47976d137bcec7b66986f51282dcb2091933f88983dd7eb89e59c4", + "sha256:39dd836134e261ec769cd5aa9ae7a3a330a7dac81efb66eb5504643abd8235df", + "sha256:421ed75e54ebecd79c80178c1df5bdbe1e0e3e10e7efef5f011b5f0be6a9a12f", + "sha256:421f211fb15aeb836b4ba61174cb409fc82222ab3b2486deb4953ae863e6507b", + "sha256:43e8bc75773ca06ee7a64602b799613171e4edf4d9d8fd38fa5c49f1cdbb4407", + "sha256:46b82fe50eb8171ee2205e935f3fd5900e31beb5e54e10c88f23a5420902467d", + "sha256:4bc8720f3a224c27bd413a930b9bec5f225fda050641258967b1ebb252a053fb", + "sha256:4d10dd75fad48555e9530c5565c7ccf13754adad2fe331feefb263055cdca7b3", + "sha256:534b8b253dbdb746c06bab28383db31d7e2b42aa9b33ed4e7836319622dcd75b", + "sha256:570a3cf4e016dcb0fc561991833e5170a2a0bc6ee88fe5667591f356bd7b7895", + "sha256:5de90f8b6c7fc1d28dbe74c29b1d5053a7a8703cbc2c6f4f112907ffd7529f8e", + "sha256:603daa737579cf69efb368fab716cdce18d0b2615af77bb623f5f42aa546b3d8", + "sha256:6485c6b091504c0e6431a9495309271626eaa9ecb23276903486824f94f4c551", + "sha256:6ca6ddae1302477879099d4c4efc65790f4610d71ceff7fbe8f8b60f6ac6dcff", + "sha256:6df9eccc99a0d7bc091b58cff2f507b89f076d657253975fa2ca9eb42dbb4733", + "sha256:6f589af70286ec6565e3415145a03abc3c14a23ed7ed198ac741de81af332f26", + "sha256:731800acfc6112132aa2b7d08f9d6fe49a0c0071b30985809d084e238af98dac", + "sha256:74d34340323996b70dbd73e9530cca71c05ff7c97e30fe4d32aeea2f877836ca", + "sha256:7801ee9a9fcd47b92d4d90ff9a28cfdc23195cad72bd8032938ab3c794942b43", + "sha256:795dff9229dc024e54bd0f618f5a3adb269ee0cccd7ac9a0bef29df388beed23", + "sha256:7b3773a77ae3b18778c9bf22c7ba6478a0e5416f84b7d2ac6d764001f6d0d985", + "sha256:872697d8bff2572e4225ed8cbce17be338faac28ec1ab3c00419aaef2f56dd3c", + "sha256:8b682ed08caabfb7c042d4be083e28ddc692afb1deff5567111f8855071b75c3", + "sha256:8e846a8272bf02202794fe22beaf389ed27c2d8ebf59aafb43af4935feac0389", + "sha256:8efed581f2ee74f1e0ec04a10e97881b93abc258d13b15ef966aee71732ac152", + "sha256:970dc23ca1c64611d35a3abe76a059cf551da53d62faefd84c5bf3e0af1602d1", + "sha256:9b56eeff51efa556ecc186260ac486a4ddd79ad37bc88d669e96c45190f3c0da", + "sha256:9fc313b1a5547c5416982853f2de1454980704f3ab3dbcad18dacdc565a2eafc", + "sha256:a74e70bf3fd50a413fdce4264e037b8e8f34cb8d9207ac364167b6eb076c14ec", + "sha256:a77796b4c5370cedd4fad2264b6d7a78cb40229c7fa3cbcab24df3adea768962", + "sha256:a81f14f213a75597f9be44feb97740a51adda558465fb159114472dc2ab39ef8", + "sha256:acfc4a02ddf22f6df7184441b39f38c31e95aa8af41de4d2f825821ab1fb85c6", + "sha256:b3861996b06b8238f799b4f1bd9542d1a8ae8e4765adbdde25ed011c3bda11df", + "sha256:b901f12380e988da07f21bb6b75da7f91fd9feffb43fcf70fad698e40a2ef3a7", + "sha256:b99f4291e59dcbe548be5a1e8c6a1a19a860184526c2d14fc374ec687b98ad7d", + "sha256:bb378b0f133e19d437eca4327bac7c3f38e30950c5c604092c72b18cba839bc2", + "sha256:bbb49772fc3896850a704215160df8316db89e5e8876b2d8af6c6c15b4e0f6ea", + "sha256:bc1684b9f7ec92d2ae94a137ec311bd2227f684429521061af7ceed4952c7f72", + "sha256:bdabbe7fd66886943393ecf98318d7801dd40183af80314acd4464bccdd44d53", + "sha256:bf87f2a7f827656bc6e1d9888d47931aa0ae35cdc4ff33b1cec70d8d462590b3", + "sha256:c1e02170f8d358a8ddc716606760c73d55eea6bdb0cca2d97b86447e9524708b", + "sha256:c72ff262613c9a6f20e80bcf1e8bbc000b78b95a7fa301164ab3e3bd23bd936c", + "sha256:c854a206d8c3a5a959b803405760f3627bb4878450e2f36b5d35af09c89152fc", + "sha256:d5b327df02761c42399c878cd6c37f885bf0639befbd4d1ab763cd44ba1e0552", + "sha256:d5c4ca6faff0af4b5f3e7d88d13ec76f8cac36c9bcc814b8c84d9f3f951b2cf9", + "sha256:d61f287f820787d3acf60d113c5ce6e506870d9d3103bc37a74373e72ce9d7a6", + "sha256:dc23f2ac2c1ded250f1aa66fbd1a3d823f76de549978b61eed4fb34affc11338", + "sha256:dc79ed4773cd35328377a8fedbbdcafb3a9d242ee63b96863c0692c81faefab8", + "sha256:df75707f466ab6fa086f164bff2df75fd16543c8d43ca43a268f938c1144e792", + "sha256:e6a74cb618da93ac1322d6a548a4508e76eb4c388ed1c80560bc25d8764cf272", + "sha256:e89a814f1727be7d543ac6910f0d94131f43a337e811ab684606d42dbc22b701", + "sha256:efe75e3b8768c4f9d454d3c1b2b2a67e757f2b00d638146d3a4cddb38460fc3a", + "sha256:f2f950ca403ffaa808a017e40e3371115bcb0b4b1061772b03e7d842555132ac", + "sha256:f4428b6808d781f4b605a27f53fc10a3ca343d1cd901c691b9ba2e4ed85a5fc7", + "sha256:f46ba61c942ee64198444c9562c5cf089eaf97f17b413e15fa1c0614df304734", + "sha256:f472da992a6ba58381c0314b994c01d20e522ff8836417ef1c0975bdae142406", + "sha256:f58e489e43c9a1688c7d5ceb7455b44952d87f183b7b9c915b301478a2b3bfbe", + "sha256:f8576a1dcf445ef064bf8c3b2cdc1d6353e41cb4b366329946883e285dcbcec0", + "sha256:fa787b414c4dc6b6cd75338fac18a7dbb53a09443dd863020a2d2bda76940ca6", + "sha256:fda423836d7d69cba6a6f99e7a34c2e5fe3621e5e945cd25ea9ba60a96223254" + ], + "markers": "python_version >= '3.6'", + "version": "==1.0.1" + }, + "pycryptodomex": { + "hashes": [ + "sha256:160a39a708c36fa0b168ab79386dede588e62aec06eb505add870739329aecc6", + "sha256:192306cf881fe3467dda0e174a4f47bb3a8bb24b90c9cdfbdc248eec5fc0578c", + "sha256:1949e09ea49b09c36d11a951b16ff2a05a0ffe969dda1846e4686ee342fe8646", + "sha256:215be2980a6b70704c10796dd7003eb4390e7be138ac6fb8344bf47e71a8d470", + "sha256:27072a494ce621cc7a9096bbf60ed66826bb94db24b49b7359509e7951033e74", + "sha256:2dc4eab20f4f04a2d00220fdc9258717b82d31913552e766d5f00282c031b70a", + "sha256:302a8f37c224e7b5d72017d462a2be058e28f7be627bdd854066e16722d0fc0c", + "sha256:3d9314ac785a5b75d5aaf924c5f21d6ca7e8df442e5cf4f0fefad4f6e284d422", + "sha256:3e3ecb5fe979e7c1bb0027e518340acf7ee60415d79295e5251d13c68dde576e", + "sha256:4d9379c684efea80fdab02a3eb0169372bca7db13f9332cb67483b8dc8b67c37", + "sha256:50308fcdbf8345e5ec224a5502b4215178bdb5e95456ead8ab1a69ffd94779cb", + "sha256:5594a125dae30d60e94f37797fc67ce3c744522de7992c7c360d02fdb34918f8", + "sha256:58fc0aceb9c961b9897facec9da24c6a94c5db04597ec832060f53d4d6a07196", + "sha256:6421d23d6a648e83ba2670a352bcd978542dad86829209f59d17a3f087f4afef", + "sha256:6875eb8666f68ddbd39097867325bd22771f595b4e2b0149739b5623c8bf899b", + "sha256:6ed3606832987018615f68e8ed716a7065c09a0fe94afd7c9ca1b6777f0ac6eb", + "sha256:71687eed47df7e965f6e0bf3cadef98f368d5221f0fb89d2132effe1a3e6a194", + "sha256:73d64b32d84cf48d9ec62106aa277dbe99ab5fbfd38c5100bc7bddd3beb569f7", + "sha256:75672205148bdea34669173366df005dbd52be05115e919551ee97171083423d", + "sha256:76f0a46bee539dae4b3dfe37216f678769349576b0080fdbe431d19a02da42ff", + "sha256:8ff129a5a0eb5ff16e45ca4fa70a6051da7f3de303c33b259063c19be0c43d35", + "sha256:ac614363a86cc53d8ba44b6c469831d1555947e69ab3276ae8d6edc219f570f7", + "sha256:ba95abd563b0d1b88401658665a260852a8e6c647026ee6a0a65589287681df8", + "sha256:bbdcce0a226d9205560a5936b05208c709b01d493ed8307792075dedfaaffa5f", + "sha256:bec6c80994d4e7a38312072f89458903b65ec99bed2d65aa4de96d997a53ea7a", + "sha256:c2953afebf282a444c51bf4effe751706b4d0d63d7ca2cc51db21f902aa5b84e", + "sha256:d35a8ffdc8b05e4b353ba281217c8437f02c57d7233363824e9d794cf753c419", + "sha256:d56c9ec41258fd3734db9f5e4d2faeabe48644ba9ca23b18e1839b3bdf093222", + "sha256:d84e105787f5e5d36ec6a581ff37a1048d12e638688074b2a00bcf402f9aa1c2", + "sha256:e00a4bacb83a2627e8210cb353a2e31f04befc1155db2976e5e239dd66482278", + "sha256:f237278836dda412a325e9340ba2e6a84cb0f56b9244781e5b61f10b3905de88", + "sha256:f9ab5ef0718f6a8716695dea16d83b671b22c45e9c0c78fd807c32c0192e54b5" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==3.18.0" + }, + "pypdf2": { + "hashes": [ + "sha256:a74408f69ba6271f71b9352ef4ed03dc53a31aa404d29b5d31f53bfecfee1440", + "sha256:d16e4205cfee272fbdc0568b68d82be796540b1537508cef59388f839c191928" + ], + "index": "pypi", + "version": "==3.0.1" + }, + "pypinyin": { + "hashes": [ + "sha256:3791f5b647e446866e6b5d3a63942ecbaeb29b49fed268a9d9a4982241c35fe0", + "sha256:a5d61a79c5f48f6b4a422f010c20d48fcd53c705784df4aa80e329493219a4be" + ], + "index": "pypi", + "version": "==0.49.0" + }, + "pypng": { + "hashes": [ + "sha256:4a43e969b8f5aaafb2a415536c1a8ec7e341cd6a3f957fd5b5f32a4cfeed902c", + "sha256:739c433ba96f078315de54c0db975aee537cbc3e1d0ae4ed9aab0ca1e427e2c1" + ], + "version": "==0.20220715.0" + }, + "pyppmd": { + "hashes": [ + "sha256:024f714ebb8ddf59dae164adc3c220c24555d470f4adb5bd022abc50298cfff3", + "sha256:05950d8a39fd9bf6c64572d69a6dd0a1af3fadf8d4a2a0bb62f5b04c0a618300", + "sha256:075c9bd297e3b0a87dd7aeabca7fee668218acbe69ecc1c6511064558de8840f", + "sha256:07e067e114f05918c8a4ab1fa6a070e2c7a9e497aa73fbf6d87a90e7a6e62a57", + "sha256:09668aa43e4f02b8725e6233dfc66e532c72f0e69fa1b34dd814a9f7200e0496", + "sha256:0e9c001719527dbafdd7fd8709b98bd63c173451c2eddbaa77abf62486a13da0", + "sha256:10c8a41093952cde52b6d89488dc601ee7b10f6c95c430488f68987393777b46", + "sha256:12a783a0e3c76484a1bc93783867a36ab9a60de5b5298d57c9fe7348e848346e", + "sha256:12be01e919a34c6944568592b35451acf7c98ed18e005bb4b1c046ed520aff7f", + "sha256:1658714d012a5f9a8a3e67f3a9ede3519a2558064ccbd3163c39aca0cfd2412b", + "sha256:18d7cf4d0a9ced96ff1fa44de9ee3d65f5b06278c8f9a61c3edeb660f12f146b", + "sha256:18f863d58c4451e00765137be731c2b2150aff829468f59de4169e052429e1fd", + "sha256:1c0fd06aaf782e65b7b5bbc47f8a9dbe050c1ba18474ccbe0a2b37f57a8d8c72", + "sha256:1ec00b07c6b68feb402d6596f3575a7892ad69e4f455deee7b5301df703e60dd", + "sha256:2858471a291b51fab49242d78bd67c2b7719368618a02e4aa995de8c855da73c", + "sha256:2c31e5b331923f3b3b2cfbc66a60ecfd73db1a19a646bd1faf25bfde709a80d0", + "sha256:31a09fd1b10518342ff442b57dd8c890b9bfea6bbdbb785c729f0d139092e42e", + "sha256:385a0b1341ebdfd7cb101c43eea130546830073c01bdb5036bca45c033ee633e", + "sha256:3bc75ed4e969b09fd1a766dd79cb3d5efe56edc11c86ac0b357b5648c7181ce2", + "sha256:3ea6a0d26db17027805a804d013cf761d732df5bce9d6b314cd1c727fe347277", + "sha256:3ecb83e0cc92960f959111518ea208b51a58e8cc303ff959e9cd2cc56dd36a63", + "sha256:47ea218f7dfa94d15286c25d60db3091db1082ba958fa0a32ccaaaeaca7fc712", + "sha256:5331a7780d3444d7029e15e68385c94d6a26f688c1e87a5a9ee2e836ea6e4559", + "sha256:556b6a3af3fca2b41ca25f51c481e5df8df4da842fc5a567da7bb099cfa52423", + "sha256:5805c73590fb8f0ceb3e6cb115774b66a6f4700ae84b31d962ad69667e05dfbd", + "sha256:5847c47127ff9ea323f5910c62b9f136c3fab181a5144bfe72be13f051047357", + "sha256:5d90d87377d83d909eafbf23301057fe16e6662c98ffea738159a234d9000a68", + "sha256:6059ea0c9acc3b52b2961412ac75d1da72656f8b69bb8fc3d92eec6776176011", + "sha256:61acfeee5ed59796037499119edd3159bf6b8c5fcaef17e295a2ca4103112d60", + "sha256:6279f1c4b6aefacb95df49db2f2e232530592d1849c37b73478a4f26eb405d12", + "sha256:62f970173baf80aad9472c7c6edca4a021ae7965174b1c5d6f400c9571e92efc", + "sha256:63ddd5a81d6aaed9373cd9fc4de9529f10fa052aaf064ab283dc6218418cc5b5", + "sha256:68184b7246ea73a92a764e16cc18b74ccf3c8d6bfc438bbace57aeb1914118a7", + "sha256:6a0c524be57698fe61fff893d485a9af21e6bc0aa2d385b71a63ff951921d4b6", + "sha256:6e2f5ff5071e4e43c92065f383753d4ad59778816485a01ee7b29e2a1ff48140", + "sha256:6f8a3b9192714b3e4773fc49c100ca13defa2502cb38e56205eb5a131ccf555d", + "sha256:703c4fbc9b5e1454f403fb1d6b4a6c4c729f72eef14690146deecd2166429d6d", + "sha256:706d33cec3601d894f8a4a158bc652b7a3f01cd9e92c2da5d8711efeb9755835", + "sha256:71f994f281439705cb04c497adc2863551fa5813606af6fb26c673a44a36c4e3", + "sha256:74bd56b165283bb5586ff9ac7a896b217b3c94effe144b768279807840142bb4", + "sha256:7ae419f71afa88784d53dd2449882af982bbd0328fa22a7e6a339221f3143918", + "sha256:7e8d3c309061ae7fb40e4a26d30f8982b367abc562b9b8621cb79932cb3b94d9", + "sha256:7f1e7a1747518b5822eb755f3715d88bd1459e24de828aed86b7c1aa35e3ed76", + "sha256:8049c19af4b78b400b2347bff4514763257b55516c359144e9d8091991ed12e8", + "sha256:8680008b1b1e9e77f3337a1a53c1b32541cac9f93f79ae12d34de050585999ac", + "sha256:8a90b98f9d501eaedaca4d0e82f9e771bd2d780d71effcdeacc9fc6180a00e07", + "sha256:8aafe6fc436a782e6d424a0ac00de08a1559b6d6ddd08031adbe791ff4e54c90", + "sha256:93d0d6ed97046ce25d64427ec493e06c23f32838972258bf11d603c9c998d6b3", + "sha256:a5fbec6f39a307818593508d8623d9328baf494137d191fc98e11f47e058ceee", + "sha256:a63adeeb9dc4afd6d377ac1c9801f9539f9a81430e9c96d332023bf2ad6c04a1", + "sha256:a7240c00083527cf0b1bbdc92f6967e522efb9ad6968c953be174c390b091b3e", + "sha256:a7f83970a057157c88d4a53a40431d07d8d3f38029ad2eae621422f955bd243b", + "sha256:aadc63d0ac83f8c5744eb34ea47a70ff7bfab519b293482d7ccb09946c374dc7", + "sha256:ab4e29f774e064af09baf8478acd967684524e566b78fcc4f6f669757f0a2ab5", + "sha256:ac19ec1b6e3a0aadc1537466f537017189373593e23fe254df050fdd01f4a722", + "sha256:ac1aeba466617cf975cd6719070ca9721bcd83a1a84bd8cf74c3a2808724481e", + "sha256:aee9c52a6f232f3f7c683b87213aa3a9eacd281ab31187e784290ba1c05024fe", + "sha256:b0a87399ade5820f787758449976e758604c7739eb5f79ed9e594b5fa3a6a1bc", + "sha256:b6b6c01e46fcf785ad6c272be400ebcbcb434a1d91150614e10de8cc569b8bff", + "sha256:b8eee08c615ae9edd7bf1f214a377cac3d27417f22112685e581d4bab43029b0", + "sha256:bae08176e0d3ed0a5cbd838ff1ac557dfa088a652af633ab1905ab35bb9d7bc4", + "sha256:c60031de93834e5cd262db4b27272101d04a9a18c4cc49f81d483221211a97c8", + "sha256:ca6a926d229a6dbf2ccdb0d4e692d81ff927459b59a1cec14ef522522df6d757", + "sha256:ca8a842b4ff671642b63ed4edd4e1ff7dc0ba0a7af4135758233f056ab992fca", + "sha256:ccdfc8b2a1d73b2186850b9a5bd96106d5fd4419a620d344b0ab8bf630680cf8", + "sha256:cd227b8c292ac43d3297a91055fab51c27894dba39d04ccc774a72d9e6f85752", + "sha256:cecf0859b461bcf04439f32bcfb6e081016fa6204c92b5950d19d248fd1aad6b", + "sha256:cfb716a4a07ccbef84ed9fc31d012cef3b38404a6510e24d307cf64025999b21", + "sha256:d2c3c16f644afb1b3caa4f6c717682030f7c3f54a12af8b1416b21877f0b5226", + "sha256:d5c6c40f15b9fdea10bf966e5b07ee0a0ebcb8cf188ed9a466029c894816b303", + "sha256:e17b08a5c283faf48b4ee888f8fa53f919cd8afd0930eae4d59f719f6be519fb", + "sha256:ea4b1a326afe2055304740a03a233f7389f615179b9f6377264b306f619bfb11", + "sha256:f488164e8876d213b0627a7a6cb798081eaf84fd9ec6dde5a1668296b15e1a6c", + "sha256:f751882dd529328ca43af8018f79cdd02ed707fcda30a2fa9acb1ee5c48261a6", + "sha256:f79ebcd7312b541d3520e1a0d4c362731e24403e2f9f6761679b2ad819d5c706", + "sha256:f7a1b08612627d5280ef2dad1fadb0b1a10c70df0c484f9091eff5fab5e4c84e", + "sha256:f8dbe3076fe20c4d65cb1d1b51eeb17a1c177402b83100017a55daad888e198e", + "sha256:f9a3782f5accab4186d68c86defc61fcc7d0146e9cdc5b54e18656852c71db16" + ], + "markers": "python_version >= '3.6'", + "version": "==1.0.0" + }, + "pyside6": { + "hashes": [ + "sha256:0356dc73c138c25b980d716e425801a61d4aa1d9f7811cf8a825499edfd4d1ae", + "sha256:ad82cb12f805c5bec7693d6f1c0d6e4ae4b9462eb368a81bc0d3091de8fae76e", + "sha256:b509e4d3ffde4a594d70000f881452643c9aaed800bad2959882075c01f72428", + "sha256:ca259b4377eb74e4c9cb74a2afb18c37658f0c83dc18229d1f325974739f6df2", + "sha256:e6d879ca0f8827a7866856fbedd08857e4bd8f9a858dc998dea10d1913e97938", + "sha256:f0944cb0da34dc3b0bb62c7a20b6618667760eccdaa4262a90ba7f64b19fb783" + ], + "index": "pypi", + "version": "==6.5.2" + }, + "pyside6-addons": { + "hashes": [ + "sha256:15bf592e54b3409853c3db23bdb20fb14725b84d50feee862e8b2a3a8a3a0627", + "sha256:26b55a658fed94a0b3c09927b408663860aca530bf6e83040dcb8fdcc221877d", + "sha256:793a53b7db32f7780fa9571a14f0bce3700604247127cdb2125b380c577a92eb", + "sha256:a31cb7eca2e8cd80332e131124ed9ad197610a2a80eac4f2bd58e9124e38060a", + "sha256:bea98d823179b814109dcf3ac6c8a0eddf886570aa9ef8ef09b6bae37bc227ca", + "sha256:c77e06fd511cc8b9292c01724504d6806c33c1f5904bc59f5a8c560a74c15438" + ], + "markers": "python_version < '3.12' and python_version >= '3.7'", + "version": "==6.5.2" + }, + "pyside6-essentials": { + "hashes": [ + "sha256:149f1db77d82f35c6a88a403f5297645ebe737df28e163ac4dc402dfa0b961ee", + "sha256:1620e82b38714a1570b142c01694d0415a25526517b24620ff9b00c9f76cfca9", + "sha256:4be6ed964cb863823f2788717b9a6ba8f1d94eb3fa2bf1584f4621ab5e021e27", + "sha256:6b21cf78b33554f793d409d79b828d9578d34200c86f813ef1b8dac756589b71", + "sha256:c9606de7afaee57f2c0861cc4b973bd630556dd38ec0d1542560cba81c27ab81", + "sha256:e67b10f96f0ac5ed24d7e3c4985d37c70550a9dee6f38b127faf5e5341151665" + ], + "markers": "python_version < '3.12' and python_version >= '3.7'", + "version": "==6.5.2" + }, + "pyzstd": { + "hashes": [ + "sha256:00c188704141c709da96cc4a79f058d51f5318e839d6f904c7cc9badcf78e98e", + "sha256:013321ddaff083b24e43a8b06303446771978343b488ed73adf56c70a46e2783", + "sha256:0a4334e972109bdd17fb40dbdd9fcca6137648cab416fca505a2dcd186f50533", + "sha256:12668ceb8329aaa908b4d907d3a77bb748ff28b309c3b105c995a8715d535d2b", + "sha256:14121a4d95070f54bdc9a80dab1dd8fd9093907a1e687926447ca69b5b40a4d5", + "sha256:1b9cda5314982d64c856f9298be0d9bf69fbff0ca514d1651037616354b473ff", + "sha256:1cbf212253abd65e6451acdfb608adafe98ad8f05462fb9a054ddab816545caa", + "sha256:1dbe76b6d8fe75f6dbec24793fc07b1d1ae9464de9941138d5b9668f7670e6b0", + "sha256:209a92fbe892bd69cde58ffcb4861468e2c3c2d0626763e16e122bb55cb1fb1a", + "sha256:20f2dd56d46441cd9277077060c34c0b9ce3469412665ea5ccd506dd2708d994", + "sha256:23695dabdfd5081beab25754dc0105b42fbd2085a7c293901bcb45045969c5ec", + "sha256:250dad90140a6faea4cef555f339b6ceaad5cf03ed1127b8d06de214ff0db2e7", + "sha256:289e25871fe232d2482c0985a75a1faa7c92e10a6c3e3914d165f62d005d0aa6", + "sha256:2919afd114fd12309ed2f831ef6e95730ebf13c2a92d258ad055769d00ef4d7a", + "sha256:29e452caaf0de9cc17319225921d8c28cdc7a879948e990ff1e7735e7f976517", + "sha256:305c232462dbe80d0ee5ec91b1b0ec9153ec6ba6393d5348741af5d30b07ef52", + "sha256:31f60f01884350aec24e7a68f3ad089151b7a636490203c41a1a7c8e0cddd9b8", + "sha256:3351ad2feb51dcbb936defd47cab00d6f114214f224636503ed08298f30164c9", + "sha256:346f835e368e1051f8ea187ad9b49759cf6249c9ebf2f2a3861e435a568104b8", + "sha256:370b34a7c2f9c53cee494028daa5a7264690e1756a89c3855fd0be5ad298ec30", + "sha256:3a26df749589d898cd3253d2139eb85b867ddffc49286059c8bdb3cb9ce9b545", + "sha256:3bc0e7e2cccf78e562ab416daf68448b6552a5b6450a1ff3e15cabfc19254883", + "sha256:3f0fe2ef7ebc6e9b347585e414c4fefd32270ba8bdf9eb82496f3030cbdca465", + "sha256:3f72f310b10b730cddfb654006ae497e7706c81e6a7642d3da7fd2439df7d88d", + "sha256:40bdb468281a5cd525e2e990b97344f0974e0589bd1b395501c25471fcd7edda", + "sha256:4358dd80b315c82d760b44c6df7857c9c898d04e7b0c14abb0eb3692354e9379", + "sha256:441078bfd3b508597415338af667c3575980364f1286eedde58291558b9c2832", + "sha256:47c2a4c319300c381f194274203f47b12c433e1fd86b90ecdc7fb258c630f93b", + "sha256:49c57ae18f138a4b66480b2364fe6a0f2345ada919e93fc729c95c6b17ec73a4", + "sha256:4a0dcb32ac4d1d67a77ae6a2d60ea0921af7e682b3427202d8acb8e86642391c", + "sha256:4ed01beb31d5177456ec2c4b66591a0df83dbc72df29f05f40502bfefe47bbe4", + "sha256:50ccbaafee80b4f1c5c55bbe07f80871b9b8fe3499bf7357dde2c23fb1c2ac0e", + "sha256:51607d7d44f94a364ef0e3ccf9a92390def0faf6e7572eef082f15c657b5d03a", + "sha256:5345c7a697327e2fa7c37534bb2968ea84595d8ec7fc8c4a60216ec1be6e65bd", + "sha256:542808d88464d538f5d2c6b48b545a7fe15f0d20c7fa703b469d039a08c9fa10", + "sha256:5819d502dacd54114c30bc24efcb76e723b93f8f528be70851056a396a792c46", + "sha256:5aed5fc86d0bfc5f16e871cbb35ec93df61476d7fde4c1c6081015a075ecfbc1", + "sha256:5d9ec8634ab0cbfbcff535ac07555ebdae0282ad66762f0471fad11c16181e33", + "sha256:5fb00c706d0b59c53124f982bd84b7d46866a8ea2a7670aaaa1ab4dbe6001b50", + "sha256:5fd7cf79949174d1018b896638f88aea1ff2a969f87a6199ea23b25b506e26c5", + "sha256:606b2452d78f0f731566d392f8d83cd012c2ffadb2cb2e2903fdd360c1faac8a", + "sha256:6128cb653d011f3781554b70ce1f1f388cd516820fbaf8fd03ee245ecaa48349", + "sha256:639935b5b3d9ed3911493504581254b76cb578279302f7f340924ac5bfca4090", + "sha256:64564f4c175c5bb8e744de5816d69ee0b940e472160a5e665f30adc412b694f3", + "sha256:69f12ce4866a3725138e97f22f2c4cb21d3ae18cd422906cd57ed12a9ffd86c5", + "sha256:6a60ee6836599363a24367cf780ad45446b07eba49ec72d19bad761d5414aca7", + "sha256:6b9af8d62c087354abd071e01d9445ea51b31779c8a4a0d5c14ee12caee3d18f", + "sha256:6c456882baab2a48a5bfabe458a557af25d0768ff29acbe200461e84c0f697d5", + "sha256:6f281cc2f096530c339f122e0d9866545f5592dd9bffe0fade565c2771130a45", + "sha256:73877eebbdcb8259cf0099665f8c8274d4273b361371405a611fb6bd9f4d64f6", + "sha256:74455bd918e7bc9883e3178a1a8fe796308670f0ee4488c80a0d9514e13807a1", + "sha256:7452ae7e6d80e697d78d3f56d1b4d2a350286eea229afb35f55ab88b934b6acd", + "sha256:77294f0f797c97a46ffb3daff1fe097c9d5aa9f96867333978e6791286963e50", + "sha256:7ac886e04f253960ae82e38ded8352085c61d78de99412d178a94ecf475b5e5f", + "sha256:7c420878726d677da7484f6021dbe7e1f9345a791b155de632c6ce36678fb621", + "sha256:836f1d85a4b5d3689d455aeb1dc6c42acb96aaf8e5282825c00ccf2545ad5630", + "sha256:84aa6eecba967bdac167451501dcaceec548d8b8c4ca7fa41ceda4dbfc279297", + "sha256:866ba6ce85f337fa1677516217b6f10fc25e19acb6e17a501d5822e66396bdd5", + "sha256:86e0e65e205793b337d62d9764700dfd02b5f83b01e26ad345736e7ac0554ebd", + "sha256:87a1a4ca93da414f3b6da8131e61aca6d48a4e837fb0b1cbde05ae9d13332317", + "sha256:8d3a1b6fa71a0ae7abc320d9db91b5a96a71eef1dbee0d62a6232b71c97af962", + "sha256:8f9eb97fb6fd4551ff9d5012b4fcee9abeea9c8af6b9e3ebc3c76cc2bd0a43a7", + "sha256:91453ce9476363d777b2ea2e9c6dccecd2073cf35697e048de2e8d47e1f36c7c", + "sha256:9596aeb8c71192f4fba1ca25cec420da195219398d2df811d5082559efd9561f", + "sha256:960ab83a977a44284c4ffab2820ccd6c9b332571a3d622fefa4b29b0a5de72b0", + "sha256:97e05f66c5847e6889594508298d78ddb84a0115e9234d598415dc5a06d3a4a7", + "sha256:9ac634753f6d26cba503cea7bb5b350aec7c5366f44fa68c79e9c90be9fd0ebc", + "sha256:9e1097d8b57f64878a3f176f4cd6b9a1bbe9fb2d236f1a85a4357722626d8f25", + "sha256:a1b81cc86b69ff530d45e735ed479e14704999f534ad28a39f04be4a8fe2b91f", + "sha256:a4f786f1b1ab39a0908db04ebe5b2c7cbc6f1ce07a27d3a12eb980bffd7fea7d", + "sha256:a594795ef89bd83297c860ff585f2d25580ce9805eb9cc44c831d311e7f1951a", + "sha256:a708b9e6ff1826504940beb6b5c2c9dfd4e3b55c16ab88a4572f5b9dbb64cc56", + "sha256:a90b901ccfd24b028faea19c927ff03f3cfefe82ba0b931fbb8da4ef0664911b", + "sha256:ae3d0575721a372c20130681bfaf873225fd9e1c290b7d56b7e0c14f413318f6", + "sha256:afef9eb882cf3b395eef9c85b737a4acd09528975e6a5d9faedf28874ca65f52", + "sha256:aff1b469187f6c789cdf17cd95c9b24e87396dc86953b1cf38b9a05cea873c80", + "sha256:b2ae8993f3863632d31ca8921c8a5dc9ecc5551c7b88895cefb5a26d17643391", + "sha256:b2dd39e12f7467a7422ce50711524759d4d22016714cbae6a7096b954bc2fa32", + "sha256:b4de7741d542a477387299bf9450e8be3e768c352d6b3438254eb02af1e59462", + "sha256:b5b517fbbc5d223fc36041673e7c2a0d3a82be6a5464a5f0599069330b76f97d", + "sha256:bdc09de97b1b3f6c3d87fec04d6fe29dd4fefe6b354ad2d822fc369b8aa0942b", + "sha256:c249741b10eb714578d765487b767e0e7fcc2ac84a299209a6073566e730dbea", + "sha256:c2b093a74b10232c70b5d29814fcee6544bb6f30e2d922d26db9ab4b4cd00c04", + "sha256:c31f6dd5bd60688d51487a3f5e2ae29ed1948926e44d7a2316b193b083f80d5d", + "sha256:c41e5457f4de5d38a270bc44619873589bbe6fe251225deec583ed20199df0f3", + "sha256:c46e77c2ad614a0399503dc675d72436cbf6332a20d49a0e5bad03058d6cbfad", + "sha256:c9589cb79d4e401630481755c92b072aa7ba5505ec81dec865ef43932ec037e4", + "sha256:ca19213785f864781848e0216cba07e97f563f60a50bbc7885b54461d8c64873", + "sha256:cbfdde6c5768ffa5d2f14127bbc1d7c3c2d03c0ceaeb0736946197e06275ccc7", + "sha256:cd6a8d43a0c294918e3afb7e4b1d8c04d2e4c3ea9ddf05475fdaf366c7e5b3a6", + "sha256:cffaab46f9e04856dc3daa6097bfb3d3bea0b1771237e869c57b13f3dcc2c238", + "sha256:d0929302d187bfeca335b7f710f774f1b2ea3f610b2a80e8a1ac2da216cd9766", + "sha256:d44a7d4586f02b630658298c089ff755e74d0677b93c71e09d33dd35bdd4987a", + "sha256:d7ddbf234c9adc72189bb552d830e9a0c2c4401b5baf7b003eacd5c552ddcc00", + "sha256:dca286c6c1ca5febf13f5f2ae7e8aa7536e49bd07f4232796651a43ff741ceca", + "sha256:dcb2172ca8b62f82af9d1f8db80c21c64c5ba3991935caefde88bb378f0afb51", + "sha256:e4e00c1600022b47ef0e9e1f893cb0c2322209ec6c1581a3e3f63ed78330ddf0", + "sha256:e789e19095b818f7126180b4387c0f01700c3ad2378a4e7649b2ddf4bf47ffbc", + "sha256:e79babb67b415aa54abb213897ceaa011515a5f3e146a2a97f4e6486b9743af4", + "sha256:e8f75e839ee253af60b03d9957182fdd069dfaebb62b4e999bd74016f4e120bb", + "sha256:e9934277abdddf9c733267e4dcc4886de8a3302d28f390237d447e215e8ce47d", + "sha256:ef3399e0544b46d31c2a8ff14ae1fb3c3571ae1153bbbc5ddf0d242c67bde624", + "sha256:f169e166774587227255f6ffe71f5b3303ea73cde0e2c6d52e53b9e12c03d787", + "sha256:f1d8b58f00137ccbe8b828a5ede92be3f0115cef75e6bed88d4d0bd1e7a0b1fc", + "sha256:f2839c13e486e4a23b19b1d2dc4624565cec6c228bbf803c066be1106515966b", + "sha256:f66790e4b2dcfcabc0aa54dd89317ea5671cabf06aa93cbef7cbdd4d2fdb7ee3", + "sha256:f6d8a881b50bb2015e9bdba5edb0331e85d41ff44ab33cde551047480b98d748", + "sha256:f7cfc683d320402d61205a196ace77f15dcfd16b5771f8b9ffaf406868c98e78", + "sha256:f9c5fc29a5b9d61a8f0a3494172107e0e6cf23d0cb800d6285c6722ba7fc3535", + "sha256:fc92a718bccb8ce5c9eb63fca743c38f3fa4c4e47f58f0c4ada51b2474668184" + ], + "markers": "python_version >= '3.5'", + "version": "==0.15.9" + }, + "qrcode": { + "hashes": [ + "sha256:581dca7a029bcb2deef5d01068e39093e80ef00b4a61098a2182eac59d01643a", + "sha256:9dd969454827e127dbd93696b20747239e6d540e082937c90f14ac95b30f5845" + ], + "index": "pypi", + "version": "==7.4.2" + }, + "qt-material": { + "hashes": [ + "sha256:b5dbb5ade97217cf7ae336bb7c047c90beb39dc7f2048a498c916ad5f2f7ae23", + "sha256:ceddd9b3aca167e5d620644ce350c3df4c0220c5df457ab6059f634d7ac747e3" + ], + "index": "pypi", + "version": "==2.14" + }, + "requests": { + "hashes": [ + "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", + "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1" + ], + "index": "pypi", + "version": "==2.31.0" + }, + "retrying": { + "hashes": [ + "sha256:345da8c5765bd982b1d1915deb9102fd3d1f7ad16bd84a9700b85f64d24e8f3e", + "sha256:8cc4d43cb8e1125e0ff3344e9de678fefd85db3b750b81b2240dc0183af37b35" + ], + "index": "pypi", + "version": "==1.3.4" + }, + "shiboken6": { + "hashes": [ + "sha256:3fbc35ff3c19e7d39433671bfc1be3d7fa9d071bfdd0ffe1c2a4d27acd6cf6a5", + "sha256:4e073b40734a7f9cc6ac289c735362967bb45c34f591f7c0fab7cef1d62e6a66", + "sha256:558bdb3b0ccc8aac5f9029eabfd6ad8d41f93a87cd7f54395e07732fce39280d", + "sha256:ba7af130d34ea03e329baebd80982d2635aee7e58dbb07a07085509a501ed03f", + "sha256:cc89759aabafce49b629b189ed7c81986fedaac695714a4eed1aaeb16783c6a9", + "sha256:e60d8dd3292e3f87662b9e37ae5c558c519f8250da627c473b68bd8d24ed3243" + ], + "markers": "python_version < '3.12' and python_version >= '3.7'", + "version": "==6.5.2" + }, + "six": { + "hashes": [ + "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", + "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.16.0" + }, + "soupsieve": { + "hashes": [ + "sha256:1c1bfee6819544a3447586c889157365a27e10d88cde3ad3da0cf0ddf646feb8", + "sha256:89d12b2d5dfcd2c9e8c22326da9d9aa9cb3dfab0a83a024f05704076ee8d35ea" + ], + "markers": "python_version >= '3.7'", + "version": "==2.4.1" + }, + "texttable": { + "hashes": [ + "sha256:290348fb67f7746931bcdfd55ac7584ecd4e5b0846ab164333f0794b121760f2", + "sha256:b7b68139aa8a6339d2c320ca8b1dc42d13a7831a346b446cb9eb385f0c76310c" + ], + "version": "==1.6.7" + }, + "typing-extensions": { + "hashes": [ + "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36", + "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2" + ], + "markers": "python_version >= '3.7'", + "version": "==4.7.1" + }, + "urllib3": { + "hashes": [ + "sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11", + "sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4" + ], + "markers": "python_version >= '3.7'", + "version": "==2.0.4" + } + }, + "develop": { + "altgraph": { + "hashes": [ + "sha256:ad33358114df7c9416cdb8fa1eaa5852166c505118717021c6a8c7c7abbd03dd", + "sha256:c8ac1ca6772207179ed8003ce7687757c04b0b71536f81e2ac5755c6226458fe" + ], + "version": "==0.17.3" + }, + "astroid": { + "hashes": [ + "sha256:389656ca57b6108f939cf5d2f9a2a825a3be50ba9d589670f393236e0a03b91c", + "sha256:903f024859b7c7687d7a7f3a3f73b17301f8e42dfd9cc9df9d4418172d3e2dbd" + ], + "markers": "python_full_version >= '3.7.2'", + "version": "==2.15.6" + }, + "colorama": { + "hashes": [ + "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", + "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" + ], + "markers": "platform_system == 'Windows'", + "version": "==0.4.6" + }, + "dill": { + "hashes": [ + "sha256:76b122c08ef4ce2eedcd4d1abd8e641114bfc6c2867f49f3c41facf65bf19f5e", + "sha256:cc1c8b182eb3013e24bd475ff2e9295af86c1a38eb1aff128dac8962a9ce3c03" + ], + "markers": "python_version >= '3.11'", + "version": "==0.3.7" + }, + "isort": { + "hashes": [ + "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504", + "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6" + ], + "markers": "python_full_version >= '3.8.0'", + "version": "==5.12.0" + }, + "lazy-object-proxy": { + "hashes": [ + "sha256:09763491ce220c0299688940f8dc2c5d05fd1f45af1e42e636b2e8b2303e4382", + "sha256:0a891e4e41b54fd5b8313b96399f8b0e173bbbfc03c7631f01efbe29bb0bcf82", + "sha256:189bbd5d41ae7a498397287c408617fe5c48633e7755287b21d741f7db2706a9", + "sha256:18b78ec83edbbeb69efdc0e9c1cb41a3b1b1ed11ddd8ded602464c3fc6020494", + "sha256:1aa3de4088c89a1b69f8ec0dcc169aa725b0ff017899ac568fe44ddc1396df46", + "sha256:212774e4dfa851e74d393a2370871e174d7ff0ebc980907723bb67d25c8a7c30", + "sha256:2d0daa332786cf3bb49e10dc6a17a52f6a8f9601b4cf5c295a4f85854d61de63", + "sha256:5f83ac4d83ef0ab017683d715ed356e30dd48a93746309c8f3517e1287523ef4", + "sha256:659fb5809fa4629b8a1ac5106f669cfc7bef26fbb389dda53b3e010d1ac4ebae", + "sha256:660c94ea760b3ce47d1855a30984c78327500493d396eac4dfd8bd82041b22be", + "sha256:66a3de4a3ec06cd8af3f61b8e1ec67614fbb7c995d02fa224813cb7afefee701", + "sha256:721532711daa7db0d8b779b0bb0318fa87af1c10d7fe5e52ef30f8eff254d0cd", + "sha256:7322c3d6f1766d4ef1e51a465f47955f1e8123caee67dd641e67d539a534d006", + "sha256:79a31b086e7e68b24b99b23d57723ef7e2c6d81ed21007b6281ebcd1688acb0a", + "sha256:81fc4d08b062b535d95c9ea70dbe8a335c45c04029878e62d744bdced5141586", + "sha256:8fa02eaab317b1e9e03f69aab1f91e120e7899b392c4fc19807a8278a07a97e8", + "sha256:9090d8e53235aa280fc9239a86ae3ea8ac58eff66a705fa6aa2ec4968b95c821", + "sha256:946d27deaff6cf8452ed0dba83ba38839a87f4f7a9732e8f9fd4107b21e6ff07", + "sha256:9990d8e71b9f6488e91ad25f322898c136b008d87bf852ff65391b004da5e17b", + "sha256:9cd077f3d04a58e83d04b20e334f678c2b0ff9879b9375ed107d5d07ff160171", + "sha256:9e7551208b2aded9c1447453ee366f1c4070602b3d932ace044715d89666899b", + "sha256:9f5fa4a61ce2438267163891961cfd5e32ec97a2c444e5b842d574251ade27d2", + "sha256:b40387277b0ed2d0602b8293b94d7257e17d1479e257b4de114ea11a8cb7f2d7", + "sha256:bfb38f9ffb53b942f2b5954e0f610f1e721ccebe9cce9025a38c8ccf4a5183a4", + "sha256:cbf9b082426036e19c6924a9ce90c740a9861e2bdc27a4834fd0a910742ac1e8", + "sha256:d9e25ef10a39e8afe59a5c348a4dbf29b4868ab76269f81ce1674494e2565a6e", + "sha256:db1c1722726f47e10e0b5fdbf15ac3b8adb58c091d12b3ab713965795036985f", + "sha256:e7c21c95cae3c05c14aafffe2865bbd5e377cfc1348c4f7751d9dc9a48ca4bda", + "sha256:e8c6cfb338b133fbdbc5cfaa10fe3c6aeea827db80c978dbd13bc9dd8526b7d4", + "sha256:ea806fd4c37bf7e7ad82537b0757999264d5f70c45468447bb2b91afdbe73a6e", + "sha256:edd20c5a55acb67c7ed471fa2b5fb66cb17f61430b7a6b9c3b4a1e40293b1671", + "sha256:f0117049dd1d5635bbff65444496c90e0baa48ea405125c088e93d9cf4525b11", + "sha256:f0705c376533ed2a9e5e97aacdbfe04cecd71e0aa84c7c0595d02ef93b6e4455", + "sha256:f12ad7126ae0c98d601a7ee504c1122bcef553d1d5e0c3bfa77b16b3968d2734", + "sha256:f2457189d8257dd41ae9b434ba33298aec198e30adf2dcdaaa3a28b9994f6adb", + "sha256:f699ac1c768270c9e384e4cbd268d6e67aebcfae6cd623b4d7c3bfde5a35db59" + ], + "markers": "python_version >= '3.7'", + "version": "==1.9.0" + }, + "markdown-it-py": { + "hashes": [ + "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", + "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb" + ], + "markers": "python_version >= '3.8'", + "version": "==3.0.0" + }, + "mccabe": { + "hashes": [ + "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", + "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e" + ], + "markers": "python_version >= '3.6'", + "version": "==0.7.0" + }, + "mdurl": { + "hashes": [ + "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", + "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba" + ], + "markers": "python_version >= '3.7'", + "version": "==0.1.2" + }, + "pefile": { + "hashes": [ + "sha256:82e6114004b3d6911c77c3953e3838654b04511b8b66e8583db70c65998017dc", + "sha256:da185cd2af68c08a6cd4481f7325ed600a88f6a813bad9dea07ab3ef73d8d8d6" + ], + "markers": "sys_platform == 'win32'", + "version": "==2023.2.7" + }, + "platformdirs": { + "hashes": [ + "sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d", + "sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d" + ], + "markers": "python_version >= '3.7'", + "version": "==3.10.0" + }, + "pygments": { + "hashes": [ + "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692", + "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29" + ], + "markers": "python_version >= '3.7'", + "version": "==2.16.1" + }, + "pyinstaller": { + "hashes": [ + "sha256:0df43697c4914285ecd333be968d2cd042ab9b2670124879ee87931d2344eaf5", + "sha256:1fde4381155f21d6354dc450dcaa338cd8a40aaacf6bd22b987b0f3e1f96f3ee", + "sha256:24009eba63cfdbcde6d2634e9c87f545eb67249ddf3b514e0cd3b2cdaa595828", + "sha256:28d9742c37e9fb518444b12f8c8ab3cb4ba212d752693c34475c08009aa21ccf", + "sha256:2d03419904d1c25c8968b0ad21da0e0f33d8d65716e29481b5bd83f7f342b0c5", + "sha256:3a331951f9744bc2379ea5d65d36f3c828eaefe2785f15039592cdc08560b262", + "sha256:5e446df41255e815017d96318e39f65a3eb807e74a796c7e7ff7f13b6366a2e9", + "sha256:78975043edeb628e23a73fb3ef0a273cda50e765f1716f75212ea3e91b09dede", + "sha256:7fdd319828de679f9c5e381eff998ee9b4164bf4457e7fca56946701cf002c3f", + "sha256:9fc27c5a853b14a90d39c252707673c7a0efec921cd817169aff3af0fca8c127", + "sha256:cd7d5c06f2847195a23d72ede17c60857d6f495d6f0727dc6c9bc1235f2eb79c", + "sha256:e5fb17de6c325d3b2b4ceaeb55130ad7100a79096490e4c5b890224406fa42f4" + ], + "index": "pypi", + "version": "==5.13.0" + }, + "pyinstaller-hooks-contrib": { + "hashes": [ + "sha256:0c436a4c3506020e34116a8a7ddfd854c1ad6ddca9a8cd84500bd6e69c9e68f9", + "sha256:3c10df14c0f71ab388dfbf1625375b087e7330d9444cbfd2b310ba027fa0cff0" + ], + "markers": "python_version >= '3.7'", + "version": "==2023.7" + }, + "pylint": { + "hashes": [ + "sha256:73995fb8216d3bed149c8d51bba25b2c52a8251a2c8ac846ec668ce38fab5413", + "sha256:f7b601cbc06fef7e62a754e2b41294c2aa31f1cb659624b9a85bcba29eaf8252" + ], + "index": "pypi", + "version": "==2.17.5" + }, + "pywin32-ctypes": { + "hashes": [ + "sha256:3426e063bdd5fd4df74a14fa3cf80a0b42845a87e1d1e81f6549f9daec593a60", + "sha256:bf490a1a709baf35d688fe0ecf980ed4de11d2b3e37b51e5442587a75d9957e7" + ], + "markers": "sys_platform == 'win32'", + "version": "==0.2.2" + }, + "rich": { + "hashes": [ + "sha256:146a90b3b6b47cac4a73c12866a499e9817426423f57c5a66949c086191a8808", + "sha256:fb9d6c0a0f643c99eed3875b5377a184132ba9be4d61516a55273d3554d75a39" + ], + "index": "pypi", + "version": "==13.5.2" + }, + "setuptools": { + "hashes": [ + "sha256:3d4dfa6d95f1b101d695a6160a7626e15583af71a5f52176efa5d39a054d475d", + "sha256:3d8083eed2d13afc9426f227b24fd1659489ec107c0e86cec2ffdde5c92e790b" + ], + "markers": "python_version >= '3.8'", + "version": "==68.1.2" + }, + "tomlkit": { + "hashes": [ + "sha256:38e1ff8edb991273ec9f6181244a6a391ac30e9f5098e7535640ea6be97a7c86", + "sha256:712cbd236609acc6a3e2e97253dfc52d4c2082982a88f61b640ecf0817eab899" + ], + "markers": "python_version >= '3.7'", + "version": "==0.12.1" + }, + "wrapt": { + "hashes": [ + "sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0", + "sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420", + "sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a", + "sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c", + "sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079", + "sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923", + "sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f", + "sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1", + "sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8", + "sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86", + "sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0", + "sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364", + "sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e", + "sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c", + "sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e", + "sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c", + "sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727", + "sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff", + "sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e", + "sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29", + "sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7", + "sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72", + "sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475", + "sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a", + "sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317", + "sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2", + "sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd", + "sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640", + "sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98", + "sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248", + "sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e", + "sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d", + "sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec", + "sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1", + "sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e", + "sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9", + "sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92", + "sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb", + "sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094", + "sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46", + "sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29", + "sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd", + "sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705", + "sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8", + "sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975", + "sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb", + "sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e", + "sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b", + "sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418", + "sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019", + "sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1", + "sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba", + "sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6", + "sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2", + "sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3", + "sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7", + "sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752", + "sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416", + "sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f", + "sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1", + "sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc", + "sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145", + "sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee", + "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a", + "sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7", + "sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b", + "sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653", + "sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0", + "sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90", + "sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29", + "sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6", + "sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034", + "sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09", + "sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559", + "sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639" + ], + "markers": "python_version >= '3.11'", + "version": "==1.15.0" + } + } +} diff --git a/README.md b/README.md index 5d5a181..2faea11 100644 --- a/README.md +++ b/README.md @@ -17,11 +17,11 @@ **因此 将将将~ 🎉 一个好用的哔哩哔哩漫画下载器就此诞生!** -## :sparkles: 主要功能 / 特性 +## ✨ 主要功能 / 特性 +- **黑科技下载未解锁章节!** - **已打包成单个可执行文件,双击即用!** - **易操作的图形界面!~~(不用再费劲的部署环境跑命令行)~~** - **无需漫画ID,可直接关键词搜索漫画!并附带搜索词高亮!** -- **支持B站和BiliPlus两种解析方式!** - **可配置的多线程下载,速度拉满!** - **实现了应对网络波动等情况的异常重试,以及应用了指数级退让来避免在短时间大量重试被拉黑名单** - **丰富的漫画详情信息,本地漫画管理功能,一键检查更新!** @@ -73,25 +73,31 @@ - **特别提示: 毕竟是要提供 Cookie 给第三方网站托管,因此可能会有潜在的安全风险。敏感人群请不要使用自己主账号的 Cookie** - `BiliPlus` 的 `Cookie` 获取方法跟上述一致,在 [ComicWebReader](https://www.biliplus.com/manga/) 登入后在开发者工具中找到 `access_key` 粘贴到程序设置选项中的 `BiliPlus Cookie` 即可 -- **兼容性:目前只在64位的Winodw 10上测试通过,不过其他>=windows 10的版本应该都能运行,发现问题的欢迎提Issues** +- **兼容性:目前只在64位的 Winodw 10 上测试通过,不过 windows 11 应该也能运行,发现问题的欢迎提Issues** - **搜索 / 选择章节 / 下载 的功能介绍我想已经不言而喻了,这就是图形化界面的好处!** - **值得注意的是:本软件不支持断点续传和下载任务缓存的功能 ~~(毕竟一章漫画太小了,好像也没什么必要,断了不如重下)~~,所以请确保不要在下载中途关闭!** - 如果程序意外中断,可以选择把下了一半的文件都删掉(一般在目标漫画文件夹的根目录下),重新下载 - **程序缓存和日志历史文件存在 `C:\Users\AppData\Roaming\BiliBili-Manga-Downloader\` 目录下,可以通过"清空用户数据"功能一键删除** - **如果想用"本地库存"功能,需要注意的是:下载好的漫画章节名以及保存的 `元数据.json` 都不能更改,否则将会无法正确读取漫画数据** -- **🔥 下面我要隆重的推荐一款搭配本软件使用的漫画浏览器 ~~(可以说就是为了这点儿醋 我才包的这顿饺子)~~** - -
- - **NeeView** 是一款 Windows 下开源的图片浏览器,其特色是可以像翻书一样同时浏览两张照片,还支持压缩包看图、鼠标手势、触摸操作、多线程和超前查看、支持 PDF / 视频。 原生支持中文 - - 下载地址: [Microsoft Store](https://www.microsoft.com/en-us/p/neeview/9p24z53hc1jr) - - 上面是官方介绍,要我说优势就下面几点 - - 自动切页,双页浏览 - - 左右或者右左的阅读顺序一键切换 - - 鼠标左键长摁放大,自由移动放大聚焦点,滚轮调整放大倍数, 这点超爽的好嘛,吊打所有网页浏览体验 ~~(尤其是某些地方需要放大好好品鉴的时候,嗯嗯,我说的就是背景人物!)~~ - - 优秀的资源浏览器以及简明的操作逻辑和界面 - - 总之是电脑端看漫画的不二之选~ - - 唯一的缺点好像就是对条漫不太支持,也有可能是我没找到选项,有知道的小伙伴可以联系我,谢啦~ + + +## 💕 友情推荐 +**🔥 下面我要隆重的推荐一款搭配本软件使用的本地漫画浏览器 ~~(可以说就是为了这点儿醋 我才包的这顿饺子)~~** + +
+ +- **NeeView** 是一款 Windows 下开源的图片浏览器,其特色是可以像翻书一样同时浏览两张照片,还支持压缩包看图、鼠标手势、触摸操作、多线程和超前查看、支持 PDF / 视频。 原生支持中文 +- 下载地址: [**Microsoft Store**](https://www.microsoft.com/en-us/p/neeview/9p24z53hc1jr) +- 上面是官方介绍,要我说优势就下面几点 + - 自动切页,双页浏览 + - 左右或者右左的阅读顺序一键切换 + - 鼠标左键长摁放大,自由移动放大聚焦点,滚轮调整放大倍数, 这点超爽的好嘛,吊打所有网页浏览体验 ~~(尤其是某些地方需要放大好好品鉴的时候,嗯嗯,我说的就是背景人物!)~~ + - 优秀的资源浏览器以及简明的操作逻辑和界面 + - 总之是电脑端看漫画的不二之选~ +- 唯一的缺点好像就是对条漫不太支持,也有可能是我没找到选项,有知道的小伙伴可以联系我,谢啦~ ## 💡 TODO List ~~(在可见的未来...)~~ +- 更多开发进度详情请看 [**项目清单表**](https://github.com/users/Zeal-L/projects/2/views/1) - **PS: 也欢迎小伙伴们多多的在Issues里提意见,不管是Bug还是操作逻辑,界面优化等等作者统统笑纳~** - 🟦 缓存更多资源,减少网络请求 - 🟦 添加一个启动程序加载进度条 @@ -137,16 +143,16 @@ ## ⚰️ 更新记录 -![Alt](https://repobeats.axiom.co/api/embed/cc62fded834eb06fc9b30cf7ffd54eeb53d700fc.svg "Repobeats analytics image") +![Repobeats analytics image](https://repobeats.axiom.co/api/embed/cc62fded834eb06fc9b30cf7ffd54eeb53d700fc.svg "Repobeats analytics image") -[详细信息](UPDATE.md) +[**详细日志**](UPDATE.md) ## 🍻 联系方式 欢迎进群讨论程序,漫画,资源分享, 提交问题等等 - Q群号:244029317 ## 🙈 PS -- **做项目不易,求星星!求赞助!如果本项目对你有帮助,请作者喝杯☕吧~** +**做项目不易,求星星!求赞助!如果本项目对你有帮助,请作者喝杯☕吧~** diff --git a/UPDATE.md b/UPDATE.md index 3d50cb8..cb9f244 100644 --- a/UPDATE.md +++ b/UPDATE.md @@ -1,4 +1,12 @@ ## ⚰️ 更新记录 +### **[v1.3.1](https://github.com/Zeal-L/BiliBili-Manga-Downloader/releases/tag/v1.3.1)** - *2022-08-25* +- 修复bug: + - 修复了一个在保存格式为 `pdf` 时导致内存泄漏的bug ([#68][i68]) +- 优化: + - 优化下载速度和剩余时间的计算与获取, 现在更加的平稳和准确了 + - 重构下载任务相关的逻辑,减少耦合性 + +[i68]: https://github.com/Zeal-L/BiliBili-Manga-Downloader/issues/68 ### **[v1.3.0](https://github.com/Zeal-L/BiliBili-Manga-Downloader/releases/tag/v1.3.0)** - *2022-08-11* - 新增功能: diff --git a/linting.py b/linting.py deleted file mode 100644 index 450eee8..0000000 --- a/linting.py +++ /dev/null @@ -1,21 +0,0 @@ -"""Linting and return score as system code""" - -import subprocess -from os import system -from pylint.lint import Run - - -command1 = ['git', 'ls-files', '*.py'] -command2 = ['grep', '-v', 'PySide_src'] -p1 = subprocess.Popen(command1, stdout=subprocess.PIPE) -p2 = subprocess.Popen(command2, stdin=p1.stdout, stdout=subprocess.PIPE) -p1.stdout.close() -output, error = p2.communicate() - -file_paths = [path for path in output.decode().split('\n') if path] - - -results = Run(file_paths, do_exit=False) -score = round(getattr(results.linter.stats, "global_note", 0), 2) - -system(f'echo "::set-output name=score::{str(score)}"') \ No newline at end of file diff --git a/src/Comic.py b/src/Comic.py index 469bfbc..7cb93ae 100644 --- a/src/Comic.py +++ b/src/Comic.py @@ -97,11 +97,11 @@ def _() -> dict: return self.data ############################################################ - def getComicCover(self, data: dict) -> int: + def getComicCover(self, data: dict) -> bytes: """获取漫画封面图片 Returns: - QPixmap: 漫画封面图片 + bytes: 漫画封面图片 """ @retry( diff --git a/src/DownloadManager.py b/src/DownloadManager.py index 09f1374..8babc40 100644 --- a/src/DownloadManager.py +++ b/src/DownloadManager.py @@ -165,36 +165,38 @@ def __thread__EpisodeTask(self, curr_id: int, epi: Episode) -> None: self.updateTaskInfo(curr_id, rate) imgs_path.append(img_path) + + # ?########################################################### + # ? 保存图片 + if rate == 1: + if self.save_method == "PDF": + epi.saveToPDF(imgs_path) + elif self.save_method == "文件夹-图片": + epi.saveToFolder(imgs_path) + elif self.save_method == "7z压缩包": + epi.saveTo7z(imgs_path) + + epi.clearAfterSave(imgs_path) + self.signal_rate_progress.emit( { "taskID": curr_id, "rate": int(rate * 100), } ) - self.clearAfterFinish(curr_id) - # ?########################################################### - # ? 保存图片 - - if self.save_method == "PDF": - epi.saveToPDF(imgs_path) - elif self.save_method == "文件夹-图片": - epi.saveToFolder(imgs_path) - elif self.save_method == "7z压缩包": - epi.saveTo7z(imgs_path) - - epi.clearAfterSave(imgs_path) + self.clearAfterFinish(curr_id) ############################################################ + # ? 为以后的特典下载留的接口 - def createSCTask(self) -> int: - pass + # def createSCTask(self) -> int: + # pass - ############################################################ - def thread_SCTask(self) -> None: - pass + # def thread_SCTask(self) -> None: + # pass ############################################################ diff --git a/src/Episode.py b/src/Episode.py index ac9e399..5d0bd3d 100644 --- a/src/Episode.py +++ b/src/Episode.py @@ -249,6 +249,10 @@ def _(): quality=95, ) + # 关闭所有图像, 释放内存 + for img in temp_imgs: + img.close() + # 在pdf文件属性中记录章节标题作者和软件版本以及版权信息 with open(self.epi_path_pdf, "rb") as f: pdf = PdfReader(f) diff --git a/src/Utils.py b/src/Utils.py index f6e657e..8e68084 100644 --- a/src/Utils.py +++ b/src/Utils.py @@ -22,7 +22,7 @@ from ui.MainGUI import MainGUI __app_name__ = "BiliBili-Manga-Downloader" -__version__ = "1.3.0" +__version__ = "1.3.1" __author__ = "Zeal L" __copyright__ = "Copyright (C) 2023 Zeal L" @@ -30,7 +30,7 @@ # 配置全局网络请求的 timeout 以及 max retry ############################################################ -TIMEOUT_SMALL = 4 +TIMEOUT_SMALL = 5 TIMEOUT_LARGE = 10 MAX_RETRY_SMALL = 10000 diff --git a/src/ui/MainGUI.py b/src/ui/MainGUI.py index 1be88d0..14f872a 100644 --- a/src/ui/MainGUI.py +++ b/src/ui/MainGUI.py @@ -60,6 +60,7 @@ def __init__(self, app): else: self.lineEdit_save_path.setText(os.getcwd()) self.updateConfig("save_path", os.getcwd()) + logger.info(f"save_method: {self.getConfig('save_method')}") # ?########################################################### # ? 初始化UI绑定事件 diff --git a/src/ui/PySide_src/myAbout.ui b/src/ui/PySide_src/myAbout.ui index 8fc8875..09aa74a 100644 --- a/src/ui/PySide_src/myAbout.ui +++ b/src/ui/PySide_src/myAbout.ui @@ -33,7 +33,7 @@ - <html><head/><body><p align="center"><span style=" font-size:16pt; font-weight:700; color:#00aaff;">哔哩哔哩</span><span style=" font-size:16pt; font-weight:700;">漫画下载器 </span><span style=" font-size:16pt; font-weight:700; color:#aa00ff;">v1.3.0</span></p><p align="center"><span style=" font-size:14pt; font-weight:700;">作者:Zeal-L</span></p><p align="center"><span style=" font-size:12pt; font-weight:700;">项目地址:</span><a href="https://github.com/Zeal-L/BiliBili-Manga-Downloader"><span style=" font-size:12pt; text-decoration: underline; color:#0000ff;">https://github.com/Zeal-L/BiliBili-Manga-Downloader</span></a></p><p align="center"><span style=" font-size:12pt; font-weight:700;">本程序仅供学习交流使用,严禁用于商业用途</span></p><p align="center"><span style=" font-size:16pt; font-weight:700; color:#00aa00;">联系方式:</span><span style=" font-size:12pt; font-weight:700;">Q群号:</span><span style=" font-size:12pt; font-weight:700; color:#aa00ff;">244029317</span><span style=" font-size:12pt; font-weight:700;"><br/>欢迎进群讨论程序,漫画,资源分享, 提交问题等等</span></p><p align="center"><span style=" font-family:'HYWenHei-85W'; font-size:12pt; font-weight:700;">-- LICENSE --</span></p><p align="center"><span style=" font-family:'HYWenHei-85W'; font-size:12pt;">BiliBili-Manga-Downloader</span></p><p align="center"><span style=" font-family:'HYWenHei-85W'; font-size:12pt;">Copyright (C) 2023 Zeal L</span></p><p align="center"><span style=" font-family:'HYWenHei-85W'; font-size:12pt;">This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.</span></p><p align="center"><span style=" font-family:'HYWenHei-85W'; font-size:12pt;">This program is distributed in the hope that it will be useful,but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.</span></p><p align="center"><span style=" font-family:'HYWenHei-85W'; font-size:12pt;">You should have received a copy of the GNU Affero General Public License along with this program. If not, see </span><a href="https://www.gnu.org/licenses"><span style=" font-size:12pt; text-decoration: underline; color:#0000ff;">https://www.gnu.org/licenses</span></a><span style=" font-family:'HYWenHei-85W'; font-size:12pt;">.</span></p><p><br/></p></body></html> + <html><head/><body><p align="center"><span style=" font-size:16pt; font-weight:700; color:#00aaff;">哔哩哔哩</span><span style=" font-size:16pt; font-weight:700;">漫画下载器 </span><span style=" font-size:16pt; font-weight:700; color:#aa00ff;">v1.3.1</span></p><p align="center"><span style=" font-size:14pt; font-weight:700;">作者:Zeal-L</span></p><p align="center"><span style=" font-size:12pt; font-weight:700;">项目地址:</span><a href="https://github.com/Zeal-L/BiliBili-Manga-Downloader"><span style=" font-size:12pt; text-decoration: underline; color:#0000ff;">https://github.com/Zeal-L/BiliBili-Manga-Downloader</span></a></p><p align="center"><span style=" font-size:12pt; font-weight:700;">本程序仅供学习交流使用,严禁用于商业用途</span></p><p align="center"><span style=" font-size:16pt; font-weight:700; color:#00aa00;">联系方式:</span><span style=" font-size:12pt; font-weight:700;">Q群号:</span><span style=" font-size:12pt; font-weight:700; color:#aa00ff;">244029317</span><span style=" font-size:12pt; font-weight:700;"><br/>欢迎进群讨论程序,漫画,资源分享, 提交问题等等</span></p><p align="center"><span style=" font-family:'HYWenHei-85W'; font-size:12pt; font-weight:700;">-- LICENSE --</span></p><p align="center"><span style=" font-family:'HYWenHei-85W'; font-size:12pt;">BiliBili-Manga-Downloader</span></p><p align="center"><span style=" font-family:'HYWenHei-85W'; font-size:12pt;">Copyright (C) 2023 Zeal L</span></p><p align="center"><span style=" font-family:'HYWenHei-85W'; font-size:12pt;">This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.</span></p><p align="center"><span style=" font-family:'HYWenHei-85W'; font-size:12pt;">This program is distributed in the hope that it will be useful,but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.</span></p><p align="center"><span style=" font-family:'HYWenHei-85W'; font-size:12pt;">You should have received a copy of the GNU Affero General Public License along with this program. If not, see </span><a href="https://www.gnu.org/licenses"><span style=" font-size:12pt; text-decoration: underline; color:#0000ff;">https://www.gnu.org/licenses</span></a><span style=" font-family:'HYWenHei-85W'; font-size:12pt;">.</span></p><p><br/></p></body></html> true diff --git a/src/ui/PySide_src/myAbout_ui.py b/src/ui/PySide_src/myAbout_ui.py index 0d74a6d..835938e 100644 --- a/src/ui/PySide_src/myAbout_ui.py +++ b/src/ui/PySide_src/myAbout_ui.py @@ -55,7 +55,7 @@ def setupUi(self, My_about): def retranslateUi(self, My_about): My_about.setWindowTitle(QCoreApplication.translate("My_about", u"Form", None)) self.label.setText("") - self.outline.setText(QCoreApplication.translate("My_about", u"

\u54d4\u54e9\u54d4\u54e9\u6f2b\u753b\u4e0b\u8f7d\u5668 v1.3.0

\u4f5c\u8005\uff1aZeal-L

\u9879\u76ee\u5730\u5740\uff1ahttps://github.com/Zeal-L/BiliBili-Manga-Downloader

\u672c\u7a0b\u5e8f\u4ec5\u4f9b\u5b66\u4e60\u4ea4\u6d41\u4f7f\u7528\uff0c\u4e25\u7981\u7528\u4e8e\u5546\u4e1a\u7528\u9014

\u8054\u7cfb\u65b9" + self.outline.setText(QCoreApplication.translate("My_about", u"

\u54d4\u54e9\u54d4\u54e9\u6f2b\u753b\u4e0b\u8f7d\u5668 v1.3.1

\u4f5c\u8005\uff1aZeal-L

\u9879\u76ee\u5730\u5740\uff1ahttps://github.com/Zeal-L/BiliBili-Manga-Downloader

\u672c\u7a0b\u5e8f\u4ec5\u4f9b\u5b66\u4e60\u4ea4\u6d41\u4f7f\u7528\uff0c\u4e25\u7981\u7528\u4e8e\u5546\u4e1a\u7528\u9014

\u8054\u7cfb\u65b9" "\u5f0f\uff1aQ\u7fa4\u53f7\uff1a244029317
\u6b22\u8fce\u8fdb\u7fa4\u8ba8\u8bba\u7a0b\u5e8f\uff0c\u6f2b\u753b\uff0c\u8d44\u6e90\u5206\u4eab, \u63d0\u4ea4\u95ee\u9898\u7b49\u7b49

-- LICENSE --

BiliBili-Manga-Downloader

Copyright (C) 2023 Zeal L

This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (" "at your option) any later version.

This program is distributed in the hope that it will be useful,but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License along with this program. If not, see https://www.gnu.org/licenses.


", None)) # retranslateUi diff --git a/src/ui/PySide_src/resource_rc.py b/src/ui/PySide_src/resource_rc.py index 404b227..19fa9ee 100644 --- a/src/ui/PySide_src/resource_rc.py +++ b/src/ui/PySide_src/resource_rc.py @@ -7493,7 +7493,7 @@ \x00\x00\x00J\x00\x01\x00\x00\x00\x01\x00\x01\xba\x82\ \x00\x00\x01\x89\xdf\xf3dk\ \x00\x00\x00x\x00\x00\x00\x00\x00\x01\x00\x01\xbc\x12\ -\x00\x00\x01\x89\xdf\xf3\x9f3\ +\x00\x00\x01\x89\xe0Nx\x8f\ \x00\x00\x00,\x00\x00\x00\x00\x00\x01\x00\x00\xa0<\ \x00\x00\x01\x89\xdf\xf3dl\ " diff --git a/version.txt b/version.txt index 9d0fa54..c45661d 100644 --- a/version.txt +++ b/version.txt @@ -7,8 +7,8 @@ VSVersionInfo( # filevers 和 prodvers 应该总是一个包含四项的元组:(1, 2, 3, 4) # 将不需要的项目设置为零。 # 文件版本号 - filevers=(1, 3, 0, 0), - prodvers=(1, 3, 0, 0), + filevers=(1, 3, 1, 0), + prodvers=(1, 3, 1, 0), # 包含指定有效位“flags”的位掩码 mask=0x3f, # 包含指定文件布尔属性的位掩码。 @@ -32,9 +32,9 @@ VSVersionInfo( u'040904B0', # 文件说明 [StringStruct(u'FileDescription', u'哔哩哔哩漫画下载器'), - StringStruct(u'FileVersion', u'1, 3, 0'), + StringStruct(u'FileVersion', u'1, 3, 1'), # 内部名称 - StringStruct(u'InternalName', u'哔哩哔哩漫画下载器 v1.3.0'), + StringStruct(u'InternalName', u'哔哩哔哩漫画下载器 v1.3.1'), # 版权 StringStruct(u'LegalCopyright', u'Copyright (C) 2023 Zeal L'), # 原始文件名 @@ -42,7 +42,7 @@ VSVersionInfo( # 产品名称 StringStruct(u'ProductName', u'BiliBili-Manga-Downloader'), # 产品版本号 - StringStruct(u'ProductVersion', u'1.3.0')]) + StringStruct(u'ProductVersion', u'1.3.1')]) ]), VarFileInfo([VarStruct(u'Translation', [1033, 1200])]) ] From 35805325a2773f76c50afaf86df71dc08310de19 Mon Sep 17 00:00:00 2001 From: Zeal-L Date: Fri, 25 Aug 2023 20:12:25 +1000 Subject: [PATCH 8/8] =?UTF-8?q?:bug:=20=E4=BF=AE=E5=A4=8D=E4=BA=86?= =?UTF-8?q?=E4=B8=80=E4=B8=AA=E5=8F=AF=E8=83=BD=E4=BC=9A=E5=AF=BC=E8=87=B4?= =?UTF-8?q?=E4=B8=8B=E8=BD=BD=E5=AE=8C=E6=88=90=E5=90=8E=E4=B8=8B=E8=BD=BD?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E6=98=BE=E7=A4=BA=E9=94=99=E8=AF=AF=E7=9A=84?= =?UTF-8?q?bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 - src/DownloadManager.py | 4 +--- src/ui/DownloadUI.py | 7 ++----- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 2faea11..8e5447f 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,6 @@ - **兼容性:目前只在64位的 Winodw 10 上测试通过,不过 windows 11 应该也能运行,发现问题的欢迎提Issues** - **搜索 / 选择章节 / 下载 的功能介绍我想已经不言而喻了,这就是图形化界面的好处!** - **值得注意的是:本软件不支持断点续传和下载任务缓存的功能 ~~(毕竟一章漫画太小了,好像也没什么必要,断了不如重下)~~,所以请确保不要在下载中途关闭!** - - 如果程序意外中断,可以选择把下了一半的文件都删掉(一般在目标漫画文件夹的根目录下),重新下载 - **程序缓存和日志历史文件存在 `C:\Users\AppData\Roaming\BiliBili-Manga-Downloader\` 目录下,可以通过"清空用户数据"功能一键删除** - **如果想用"本地库存"功能,需要注意的是:下载好的漫画章节名以及保存的 `元数据.json` 都不能更改,否则将会无法正确读取漫画数据** diff --git a/src/DownloadManager.py b/src/DownloadManager.py index 8babc40..985cdfb 100644 --- a/src/DownloadManager.py +++ b/src/DownloadManager.py @@ -162,7 +162,6 @@ def __thread__EpisodeTask(self, curr_id: int, epi: Episode) -> None: return rate = index / len(epi.imgs_token) - self.updateTaskInfo(curr_id, rate) imgs_path.append(img_path) @@ -178,6 +177,7 @@ def __thread__EpisodeTask(self, curr_id: int, epi: Episode) -> None: epi.clearAfterSave(imgs_path) + self.updateTaskInfo(curr_id, rate) self.signal_rate_progress.emit( { "taskID": curr_id, @@ -187,14 +187,12 @@ def __thread__EpisodeTask(self, curr_id: int, epi: Episode) -> None: self.clearAfterFinish(curr_id) - ############################################################ # ? 为以后的特典下载留的接口 # def createSCTask(self) -> int: # pass - # def thread_SCTask(self) -> None: # pass diff --git a/src/ui/DownloadUI.py b/src/ui/DownloadUI.py index f6a365d..db4914a 100644 --- a/src/ui/DownloadUI.py +++ b/src/ui/DownloadUI.py @@ -73,9 +73,8 @@ def _(result: dict): to_delete.setParent(None) to_delete.deleteLater() - # ? 删除任务字典中的条目 - if rate == -1: - del self.tasks_info[taskID] + # ? 删除任务字典中的条目 + del self.tasks_info[taskID] # ? 更新总进度条的进度,速度和剩余时间 total_progress = self.downloadManager.getTotalRate() @@ -88,8 +87,6 @@ def _(result: dict): f"{self.downloadManager.getTotalRemainedTimeStr()}" ) else: - # ? 100% 后删除所有任务字典中的条目 - self.tasks_info.clear() mainGUI.label_total_progress_speed.setText("总下载速度:") mainGUI.label_total_progress_time.setText("剩余时间:")