add support for download multiply images

This commit is contained in:
2022-03-28 21:11:03 +08:00
parent ad2e199b80
commit 9493f475df
12 changed files with 543 additions and 272 deletions

9
Cargo.lock generated
View File

@@ -278,7 +278,9 @@ dependencies = [
"encode_unicode",
"libc",
"once_cell",
"regex",
"terminal_size",
"unicode-width",
"winapi",
]
@@ -766,14 +768,13 @@ dependencies = [
[[package]]
name = "indicatif"
version = "0.16.2"
version = "0.17.0-rc.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d207dc617c7a380ab07ff572a6e52fa202a2a8f355860ac9c38e23f8196be1b"
checksum = "088977cb4de4e09ea0232bd51c844490475ad94344fb0c60784f1fb380059b6d"
dependencies = [
"console",
"lazy_static",
"number_prefix",
"regex",
"unicode-width",
]
[[package]]

View File

@@ -16,7 +16,7 @@ futures-util = "0.3"
getopts = "0.2"
gettext = "0.4"
html_parser = "0.6.2"
indicatif = "0.16"
indicatif = "0.17.0-rc.10"
int-enum = { version = "0.4", optional = true }
json = "0.12"
lazy_static = "1.4"

View File

@@ -2,7 +2,7 @@
msgid ""
msgstr ""
"Project-Id-Version: pixiv_downloader\n"
"POT-Creation-Date: 2022-03-26 15:53+0800\n"
"POT-Creation-Date: 2022-03-28 21:09+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <[email protected]>\n"
@@ -115,15 +115,18 @@ msgstr ""
msgid "Can not parse expired time:"
msgstr ""
#: cookies.rs:366 data/json.rs:50 settings.rs:418 webclient.rs:302
#: cookies.rs:366 data/json.rs:51 settings.rs:418 webclient.rs:343
#: webclient.rs:345 webclient.rs:420
msgid "Failed to remove file:"
msgstr ""
#: cookies.rs:372 data/json.rs:61 settings.rs:425 webclient.rs:312
#: cookies.rs:372 data/json.rs:62 settings.rs:425 webclient.rs:354
#: webclient.rs:356 webclient.rs:430
msgid "Failed to create file:"
msgstr ""
#: cookies.rs:379 data/json.rs:67 settings.rs:431 webclient.rs:329
#: cookies.rs:379 data/json.rs:68 settings.rs:431 webclient.rs:381
#: webclient.rs:383 webclient.rs:447
msgid "Failed to write file:"
msgstr ""
@@ -131,84 +134,85 @@ msgstr ""
msgid "Failed to unescape string:"
msgstr ""
#: download.rs:25 pixiv_web.rs:58
#: download.rs:30 pixiv_web.rs:58
msgid "Failed to initialize pixiv web api client."
msgstr ""
#: download.rs:34
#: download.rs:39
msgid "Warning: Web api client not logined, some future may not work."
msgstr ""
#: download.rs:73
msgid "Failed to get page count."
msgstr ""
#: download.rs:82
msgid "Failed to get pages' data."
msgstr ""
#: download.rs:95
msgid "Failed to save metadata to JSON file."
msgstr ""
#: download.rs:110
msgid "Failed to get ugoira's data."
msgstr ""
#: download.rs:116
msgid "Can not find source link for ugoira."
msgstr ""
#: download.rs:122 download.rs:200 download.rs:274
msgid "Failed to get file name from url:"
msgstr ""
#: download.rs:138 download.rs:144
msgid "Failed to download ugoira:"
msgstr ""
#: download.rs:149
msgid "Downloaded ugoira:"
msgstr ""
#: download.rs:159
msgid "Warning: Failed to generate video's metadata:"
msgstr ""
#: download.rs:169
msgid "Failed to convert from ugoira to mp4 video file:"
msgstr ""
#: download.rs:172
msgid "Converted <src> -> <dest>"
msgstr ""
#: download.rs:175
msgid "Failed to parse frames:"
msgstr ""
#: download.rs:182
msgid "Warning: Unknown illust type:"
msgstr ""
#: download.rs:185
msgid "Warning: Failed to get illust's type."
msgstr ""
#: download.rs:194 download.rs:268
#: download.rs:58 download.rs:286 download.rs:360
msgid "Failed to get original picture's link."
msgstr ""
#: download.rs:215 download.rs:254 download.rs:294 download.rs:324
#: download.rs:64 download.rs:197 download.rs:292 download.rs:366
msgid "Failed to get file name from url:"
msgstr ""
#: download.rs:79 download.rs:115 download.rs:307 download.rs:346
#: download.rs:386 download.rs:416
msgid "Failed to add exif data to image:"
msgstr ""
#: download.rs:234 download.rs:240 download.rs:304 download.rs:310
#: pixiv_web.rs:153
#: download.rs:100 download.rs:107 download.rs:326 download.rs:332
#: download.rs:396 download.rs:402 pixiv_web.rs:153 pixiv_web.rs:169
msgid "Failed to download image:"
msgstr ""
#: download.rs:245 download.rs:315
#: download.rs:147
msgid "Failed to get page count."
msgstr ""
#: download.rs:156
msgid "Failed to get pages' data."
msgstr ""
#: download.rs:170
msgid "Failed to save metadata to JSON file."
msgstr ""
#: download.rs:185
msgid "Failed to get ugoira's data."
msgstr ""
#: download.rs:191
msgid "Can not find source link for ugoira."
msgstr ""
#: download.rs:213 download.rs:219
msgid "Failed to download ugoira:"
msgstr ""
#: download.rs:224
msgid "Downloaded ugoira:"
msgstr ""
#: download.rs:234
msgid "Warning: Failed to generate video's metadata:"
msgstr ""
#: download.rs:244
msgid "Failed to convert from ugoira to mp4 video file:"
msgstr ""
#: download.rs:247
msgid "Converted <src> -> <dest>"
msgstr ""
#: download.rs:250
msgid "Failed to parse frames:"
msgstr ""
#: download.rs:257
msgid "Warning: Unknown illust type:"
msgstr ""
#: download.rs:260
msgid "Warning: Failed to get illust's type."
msgstr ""
#: download.rs:337 download.rs:407 webclient.rs:390
msgid "Downloaded image:"
msgstr ""
@@ -220,103 +224,107 @@ msgstr ""
msgid "Failed to parse value."
msgstr ""
#: opts.rs:98
#: opts.rs:101
msgid "Warning: The specified config file not found."
msgstr ""
#: opts.rs:122
#: opts.rs:125
msgid "Usage:"
msgstr ""
#: opts.rs:124
#: opts.rs:127
msgid "Download an artwork"
msgstr ""
#: opts.rs:126
#: opts.rs:129
msgid "Fix the config file"
msgstr ""
#: opts.rs:128
#: opts.rs:131
msgid "Print all available settings"
msgstr ""
#: opts.rs:136
#: opts.rs:139
msgid "Print help message."
msgstr ""
#: opts.rs:140
#: opts.rs:143
msgid "The location of config file."
msgstr ""
#: opts.rs:146 settings_list.rs:13
#: opts.rs:149 settings_list.rs:13
msgid "The location of cookies file. Used for web API."
msgstr ""
#: opts.rs:152 settings_list.rs:14
#: opts.rs:155 settings_list.rs:14
msgid "The language of translated tags."
msgstr ""
#: opts.rs:155
#: opts.rs:158
msgid "Verbose logging."
msgstr ""
#: opts.rs:156
#: opts.rs:159
msgid "Overwrite existing file."
msgstr ""
#: opts.rs:157
#: opts.rs:160
msgid "Skip overwrite existing file."
msgstr ""
#: opts.rs:161 settings_list.rs:15
#: opts.rs:164 settings_list.rs:15
msgid "Max retry count if request failed."
msgstr ""
#: opts.rs:167 settings_list.rs:16
#: opts.rs:170 settings_list.rs:16
msgid "The interval (in seconds) between two retries."
msgstr ""
#: opts.rs:170 settings_list.rs:17
#: opts.rs:173 settings_list.rs:17
msgid "Use data from webpage first."
msgstr ""
#: opts.rs:175 settings_list.rs:20
#: opts.rs:178 settings_list.rs:20
msgid "Add/Update exif information to image files even when overwrite are disabled."
msgstr ""
#: opts.rs:180 settings_list.rs:22
#: opts.rs:183 settings_list.rs:22
msgid "Whether to enable progress bar."
msgstr ""
#: opts.rs:202
#: opts.rs:189 settings_list.rs:23
msgid "Download multiple images at the same time."
msgstr ""
#: opts.rs:210
msgid "Unknown command."
msgstr ""
#: opts.rs:212
#: opts.rs:220
msgid "Failed to parse ID:"
msgstr ""
#: opts.rs:218
#: opts.rs:226
msgid "No URL or ID specified."
msgstr ""
#: opts.rs:226
#: opts.rs:234
msgid "No detailed command specified."
msgstr ""
#: opts.rs:239
#: opts.rs:247
msgid "Unknown config subcommand."
msgstr ""
#: opts.rs:275
#: opts.rs:283
msgid "Retry count must be an non-negative integer:"
msgstr ""
#: opts.rs:284
#: opts.rs:292
msgid "Failed to parse retry interval:"
msgstr ""
#: opts.rs:298
#: opts.rs:306
msgid "Failed to parse <opt>:"
msgstr ""
@@ -356,27 +364,27 @@ msgstr ""
msgid "Failed to detect error."
msgstr ""
#: pixiv_web.rs:168 pixiv_web.rs:198
#: pixiv_web.rs:184 pixiv_web.rs:214
msgid "Artwork's data:"
msgstr ""
#: pixiv_web.rs:183 pixiv_web.rs:188
#: pixiv_web.rs:199 pixiv_web.rs:204
msgid "Failed to get artwork page:"
msgstr ""
#: pixiv_web.rs:194
#: pixiv_web.rs:210
msgid "Failed to parse artwork page."
msgstr ""
#: pixiv_web.rs:212
#: pixiv_web.rs:228
msgid "Artwork's page data:"
msgstr ""
#: pixiv_web.rs:226
#: pixiv_web.rs:242
msgid "Ugoira's data:"
msgstr ""
#: pixiv_web.rs:250
#: pixiv_web.rs:266
msgid "Warning: Failed to save cookies file:"
msgstr ""
@@ -500,7 +508,7 @@ msgstr ""
msgid "Do you want to delete file \"<file>\"?"
msgstr ""
#: utils.rs:51 webclient.rs:154
#: utils.rs:51 webclient.rs:156
msgid "Can not parse URL:"
msgstr ""
@@ -508,43 +516,43 @@ msgstr ""
msgid "Failed to get file name from path:"
msgstr ""
#: webclient.rs:114
#: webclient.rs:116
msgid "Failed to parse Set-Cookie header."
msgstr ""
#: webclient.rs:119
#: webclient.rs:121
msgid "Failed to convert to string:"
msgstr ""
#: webclient.rs:161
#: webclient.rs:163
msgid "Parameters should be object or array:"
msgstr ""
#: webclient.rs:181
#: webclient.rs:183
msgid "Parameters should be array:"
msgstr ""
#: webclient.rs:185
#: webclient.rs:187
msgid "Parameters need at least a value:"
msgstr ""
#: webclient.rs:224
#: webclient.rs:226 webclient.rs:247
msgid "Retry after <num> seconds."
msgstr ""
#: webclient.rs:228
#: webclient.rs:230 webclient.rs:251
msgid "Retry <count> times now."
msgstr ""
#: webclient.rs:242
#: webclient.rs:264 webclient.rs:282
msgid "Error when request:"
msgstr ""
#: webclient.rs:308
#: webclient.rs:334 webclient.rs:426
msgid "Downloading \"<loc>\"."
msgstr ""
#: webclient.rs:319
#: webclient.rs:366 webclient.rs:368 webclient.rs:437
msgid "Error when downloading file:"
msgstr ""

View File

@@ -1,8 +1,8 @@
msgid ""
msgstr ""
"Project-Id-Version: pixiv_downloader\n"
"POT-Creation-Date: 2022-03-26 15:53+0800\n"
"PO-Revision-Date: 2022-03-26 15:54+0800\n"
"POT-Creation-Date: 2022-03-28 21:09+0800\n"
"PO-Revision-Date: 2022-03-28 21:10+0800\n"
"Last-Translator: lifegpc <[email protected]>\n"
"Language-Team: \n"
"Language: zh_CN\n"
@@ -116,15 +116,18 @@ msgstr "无效的Cookie:"
msgid "Can not parse expired time:"
msgstr "无法解析过期时间:"
#: cookies.rs:366 data/json.rs:50 settings.rs:418 webclient.rs:302
#: cookies.rs:366 data/json.rs:51 settings.rs:418 webclient.rs:343
#: webclient.rs:345 webclient.rs:420
msgid "Failed to remove file:"
msgstr "无法删除文件:"
#: cookies.rs:372 data/json.rs:61 settings.rs:425 webclient.rs:312
#: cookies.rs:372 data/json.rs:62 settings.rs:425 webclient.rs:354
#: webclient.rs:356 webclient.rs:430
msgid "Failed to create file:"
msgstr "无法创建文件:"
#: cookies.rs:379 data/json.rs:67 settings.rs:431 webclient.rs:329
#: cookies.rs:379 data/json.rs:68 settings.rs:431 webclient.rs:381
#: webclient.rs:383 webclient.rs:447
msgid "Failed to write file:"
msgstr "无法写入文件:"
@@ -132,84 +135,85 @@ msgstr "无法写入文件:"
msgid "Failed to unescape string:"
msgstr "无法反转义字符串:"
#: download.rs:25 pixiv_web.rs:58
#: download.rs:30 pixiv_web.rs:58
msgid "Failed to initialize pixiv web api client."
msgstr "无法初始化 Pixiv 网页 API 客户端。"
#: download.rs:34
#: download.rs:39
msgid "Warning: Web api client not logined, some future may not work."
msgstr "警告:Web API 客户端未登录,一些功能可能无法工作。"
#: download.rs:73
msgid "Failed to get page count."
msgstr "无法获取页数。"
#: download.rs:82
msgid "Failed to get pages' data."
msgstr "无法获取每页数据。"
#: download.rs:95
msgid "Failed to save metadata to JSON file."
msgstr "无法将元数据保存到 JSON 文件。"
#: download.rs:110
msgid "Failed to get ugoira's data."
msgstr "无法获取动图数据。"
#: download.rs:116
msgid "Can not find source link for ugoira."
msgstr "无法获取动图的链接。"
#: download.rs:122 download.rs:200 download.rs:274
msgid "Failed to get file name from url:"
msgstr "无法从 URL 获取文件名:"
#: download.rs:138 download.rs:144
msgid "Failed to download ugoira:"
msgstr "无法下载动图:"
#: download.rs:149
msgid "Downloaded ugoira:"
msgstr "已下载动图:"
#: download.rs:159
msgid "Warning: Failed to generate video's metadata:"
msgstr "警告:无法生成视频元数据:"
#: download.rs:169
msgid "Failed to convert from ugoira to mp4 video file:"
msgstr "无法将动图转换为 mp4 视频文件:"
#: download.rs:172
msgid "Converted <src> -> <dest>"
msgstr "已转换 <src> -> <dest>"
#: download.rs:175
msgid "Failed to parse frames:"
msgstr "无法解析帧数据:"
#: download.rs:182
msgid "Warning: Unknown illust type:"
msgstr "警告:未知的插图类型:"
#: download.rs:185
msgid "Warning: Failed to get illust's type."
msgstr "警告:无法获取插图类型。"
#: download.rs:194 download.rs:268
#: download.rs:58 download.rs:286 download.rs:360
msgid "Failed to get original picture's link."
msgstr "无法获取原图链接。"
#: download.rs:215 download.rs:254 download.rs:294 download.rs:324
#: download.rs:64 download.rs:197 download.rs:292 download.rs:366
msgid "Failed to get file name from url:"
msgstr "无法从 URL 获取文件名:"
#: download.rs:79 download.rs:115 download.rs:307 download.rs:346
#: download.rs:386 download.rs:416
msgid "Failed to add exif data to image:"
msgstr "无法往图片增加 EXIF 数据:"
#: download.rs:234 download.rs:240 download.rs:304 download.rs:310
#: pixiv_web.rs:153
#: download.rs:100 download.rs:107 download.rs:326 download.rs:332
#: download.rs:396 download.rs:402 pixiv_web.rs:153 pixiv_web.rs:169
msgid "Failed to download image:"
msgstr "无法下载图片:"
#: download.rs:245 download.rs:315
#: download.rs:147
msgid "Failed to get page count."
msgstr "无法获取页数。"
#: download.rs:156
msgid "Failed to get pages' data."
msgstr "无法获取每页数据。"
#: download.rs:170
msgid "Failed to save metadata to JSON file."
msgstr "无法将元数据保存到 JSON 文件。"
#: download.rs:185
msgid "Failed to get ugoira's data."
msgstr "无法获取动图数据。"
#: download.rs:191
msgid "Can not find source link for ugoira."
msgstr "无法获取动图的链接。"
#: download.rs:213 download.rs:219
msgid "Failed to download ugoira:"
msgstr "无法下载动图:"
#: download.rs:224
msgid "Downloaded ugoira:"
msgstr "已下载动图:"
#: download.rs:234
msgid "Warning: Failed to generate video's metadata:"
msgstr "警告:无法生成视频元数据:"
#: download.rs:244
msgid "Failed to convert from ugoira to mp4 video file:"
msgstr "无法将动图转换为 mp4 视频文件:"
#: download.rs:247
msgid "Converted <src> -> <dest>"
msgstr "已转换 <src> -> <dest>"
#: download.rs:250
msgid "Failed to parse frames:"
msgstr "无法解析帧数据:"
#: download.rs:257
msgid "Warning: Unknown illust type:"
msgstr "警告:未知的插图类型:"
#: download.rs:260
msgid "Warning: Failed to get illust's type."
msgstr "警告:无法获取插图类型。"
#: download.rs:337 download.rs:407 webclient.rs:390
msgid "Downloaded image:"
msgstr "已下载图片:"
@@ -221,104 +225,108 @@ msgstr "无法解析从字符串解析时长。"
msgid "Failed to parse value."
msgstr "无法解析值。"
#: opts.rs:98
#: opts.rs:101
msgid "Warning: The specified config file not found."
msgstr "警告:没有找到指定的设置文件。"
#: opts.rs:122
#: opts.rs:125
msgid "Usage:"
msgstr "使用方法:"
#: opts.rs:124
#: opts.rs:127
msgid "Download an artwork"
msgstr "下载一个作品"
#: opts.rs:126
#: opts.rs:129
msgid "Fix the config file"
msgstr "修复设置文件"
#: opts.rs:128
#: opts.rs:131
msgid "Print all available settings"
msgstr "打印所有可用的设置"
#: opts.rs:136
#: opts.rs:139
msgid "Print help message."
msgstr "打印帮助信息。"
#: opts.rs:140
#: opts.rs:143
msgid "The location of config file."
msgstr "设置文件的位置。"
#: opts.rs:146 settings_list.rs:13
#: opts.rs:149 settings_list.rs:13
msgid "The location of cookies file. Used for web API."
msgstr "cookies 文件的位置。用于网页 API。"
#: opts.rs:152 settings_list.rs:14
#: opts.rs:155 settings_list.rs:14
msgid "The language of translated tags."
msgstr "翻译后的标签语言。"
#: opts.rs:155
#: opts.rs:158
msgid "Verbose logging."
msgstr "启用详细内容输出。"
#: opts.rs:156
#: opts.rs:159
msgid "Overwrite existing file."
msgstr "覆盖已有文件。"
#: opts.rs:157
#: opts.rs:160
msgid "Skip overwrite existing file."
msgstr "跳过覆盖已有文件。"
#: opts.rs:161 settings_list.rs:15
#: opts.rs:164 settings_list.rs:15
msgid "Max retry count if request failed."
msgstr "请求失败时最大重试次数。"
#: opts.rs:167 settings_list.rs:16
#: opts.rs:170 settings_list.rs:16
msgid "The interval (in seconds) between two retries."
msgstr "两次尝试的间隔时间(单位:秒)。"
#: opts.rs:170 settings_list.rs:17
#: opts.rs:173 settings_list.rs:17
msgid "Use data from webpage first."
msgstr "优先使用来自网页的数据。"
#: opts.rs:175 settings_list.rs:20
#: opts.rs:178 settings_list.rs:20
msgid ""
"Add/Update exif information to image files even when overwrite are disabled."
msgstr "添加或更新图片的 EXIF 信息,即使不覆盖图片文件。"
#: opts.rs:180 settings_list.rs:22
#: opts.rs:183 settings_list.rs:22
msgid "Whether to enable progress bar."
msgstr "是否启用进度条。"
#: opts.rs:202
#: opts.rs:189 settings_list.rs:23
msgid "Download multiple images at the same time."
msgstr "同时下载多张图片。"
#: opts.rs:210
msgid "Unknown command."
msgstr "未知指令。"
#: opts.rs:212
#: opts.rs:220
msgid "Failed to parse ID:"
msgstr "无法解析 ID:"
#: opts.rs:218
#: opts.rs:226
msgid "No URL or ID specified."
msgstr "没有指定网址或 ID。"
#: opts.rs:226
#: opts.rs:234
msgid "No detailed command specified."
msgstr "没有指定更详细的指令。"
#: opts.rs:239
#: opts.rs:247
msgid "Unknown config subcommand."
msgstr "未知的 config 子指令。"
#: opts.rs:275
#: opts.rs:283
msgid "Retry count must be an non-negative integer:"
msgstr "重试次数不能是负数:"
#: opts.rs:284
#: opts.rs:292
msgid "Failed to parse retry interval:"
msgstr "无法解析间隔时间:"
#: opts.rs:298
#: opts.rs:306
msgid "Failed to parse <opt>:"
msgstr "无法解析 <opt> :"
@@ -358,27 +366,27 @@ msgstr "网络错误:"
msgid "Failed to detect error."
msgstr "检测 error 字段失败。"
#: pixiv_web.rs:168 pixiv_web.rs:198
#: pixiv_web.rs:184 pixiv_web.rs:214
msgid "Artwork's data:"
msgstr "作品数据:"
#: pixiv_web.rs:183 pixiv_web.rs:188
#: pixiv_web.rs:199 pixiv_web.rs:204
msgid "Failed to get artwork page:"
msgstr "无法获取作品页:"
#: pixiv_web.rs:194
#: pixiv_web.rs:210
msgid "Failed to parse artwork page."
msgstr "无法解析作品页。"
#: pixiv_web.rs:212
#: pixiv_web.rs:228
msgid "Artwork's page data:"
msgstr "作品页面数据:"
#: pixiv_web.rs:226
#: pixiv_web.rs:242
msgid "Ugoira's data:"
msgstr "动图数据:"
#: pixiv_web.rs:250
#: pixiv_web.rs:266
msgid "Warning: Failed to save cookies file:"
msgstr "警告:无法保存 cookies 文件:"
@@ -506,7 +514,7 @@ msgstr "无法转换路径。"
msgid "Do you want to delete file \"<file>\"?"
msgstr "你想要删除文件 <file> 吗?"
#: utils.rs:51 webclient.rs:154
#: utils.rs:51 webclient.rs:156
msgid "Can not parse URL:"
msgstr "无法解析 URL:"
@@ -514,43 +522,43 @@ msgstr "无法解析 URL:"
msgid "Failed to get file name from path:"
msgstr "无法从路径获取文件名:"
#: webclient.rs:114
#: webclient.rs:116
msgid "Failed to parse Set-Cookie header."
msgstr "无法解析 Set-Cookie 头部。"
#: webclient.rs:119
#: webclient.rs:121
msgid "Failed to convert to string:"
msgstr "无法转换为字符串:"
#: webclient.rs:161
#: webclient.rs:163
msgid "Parameters should be object or array:"
msgstr "参数应该是对象或数组:"
#: webclient.rs:181
#: webclient.rs:183
msgid "Parameters should be array:"
msgstr "参数应该是数组:"
#: webclient.rs:185
#: webclient.rs:187
msgid "Parameters need at least a value:"
msgstr "参数需要至少含有一个值:"
#: webclient.rs:224
#: webclient.rs:226 webclient.rs:247
msgid "Retry after <num> seconds."
msgstr "<num> 秒后重试。"
#: webclient.rs:228
#: webclient.rs:230 webclient.rs:251
msgid "Retry <count> times now."
msgstr "正在进行第 <count> 次重试。"
#: webclient.rs:242
#: webclient.rs:264 webclient.rs:282
msgid "Error when request:"
msgstr "请求时发生错误:"
#: webclient.rs:308
#: webclient.rs:334 webclient.rs:426
msgid "Downloading \"<loc>\"."
msgstr "正在下载 \"<loc>\"。"
#: webclient.rs:319
#: webclient.rs:366 webclient.rs:368 webclient.rs:437
msgid "Error when downloading file:"
msgstr "下载文件时发生错误:"

View File

@@ -8,7 +8,7 @@ use std::convert::TryInto;
use xml::unescape;
/// Pixiv's basic data
pub struct PixivData<'a> {
pub struct PixivData {
/// ID
pub id: PixivID,
/// The title
@@ -16,11 +16,11 @@ pub struct PixivData<'a> {
/// The author
pub author: Option<String>,
pub description: Option<String>,
helper: OptHelper<'a>,
helper: OptHelper,
}
impl<'a> PixivData<'a> {
pub fn new<T: ToPixivID>(id: T, helper: OptHelper<'a>) -> Option<Self> {
impl PixivData {
pub fn new<T: ToPixivID>(id: T, helper: OptHelper) -> Option<Self> {
let i = id.to_pixiv_id();
if i.is_none() {
return None;

View File

@@ -12,6 +12,7 @@ use std::fs::File;
use std::fs::remove_file;
use std::io::Write;
use std::path::Path;
use std::sync::Arc;
/// Store metadata informations in JSON file
pub struct JSONDataFile {
@@ -71,13 +72,19 @@ impl JSONDataFile {
}
}
impl<'a> From<PixivData<'a>> for JSONDataFile {
impl From<PixivData> for JSONDataFile {
fn from(p: PixivData) -> Self {
JSONDataFile::from(&p)
}
}
impl<'a> From<&'a PixivData<'a>> for JSONDataFile {
impl From<Arc<PixivData>> for JSONDataFile {
fn from(p: Arc<PixivData>) -> Self {
JSONDataFile::from(p.as_ref())
}
}
impl From<&PixivData> for JSONDataFile {
fn from(p: &PixivData) -> Self {
let mut f = Self {
id: p.id.clone(),

View File

@@ -7,6 +7,7 @@ use crate::data::json::JSONDataFile;
#[cfg(feature = "ugoira")]
use crate::data::video::get_video_metadata;
use crate::gettext;
use crate::opthelper::OptHelper;
use crate::pixiv_link::PixivID;
use crate::pixiv_web::PixivWebClient;
#[cfg(feature = "ugoira")]
@@ -15,20 +16,24 @@ use crate::utils::ask_need_overwrite;
use crate::utils::get_file_name_from_url;
use crate::webclient::WebClient;
use crate::Main;
use indicatif::MultiProgress;
use json::JsonValue;
use spin_on::spin_on;
use std::path::PathBuf;
use std::sync::Arc;
use futures_util::lock::Mutex;
impl Main {
pub fn download(&mut self) -> i32 {
let mut pw = PixivWebClient::new(&self);
if !pw.init() {
let pw = Arc::new(Mutex::new(PixivWebClient::new(&self)));
if !pw.try_lock().unwrap().init() {
println!("{}", gettext("Failed to initialize pixiv web api client."));
return 1;
}
if !pw.check_login() {
if !pw.try_lock().unwrap().check_login() {
return 1;
}
if !pw.logined() {
if !pw.try_lock().unwrap().logined() {
println!(
"{}",
gettext("Warning: Web api client not logined, some future may not work.")
@@ -37,7 +42,7 @@ impl Main {
for id in self.cmd.as_ref().unwrap().ids.iter() {
match id {
PixivID::Artwork(id) => {
let r = self.download_artwork(&mut pw, id.clone());
let r = self.download_artwork(Arc::clone(&pw), id.clone());
if r != 0 {
return r;
}
@@ -47,18 +52,87 @@ impl Main {
0
}
pub fn download_artwork(&self, pw: &mut PixivWebClient, id: u64) -> i32 {
pub async fn download_artwork_page(pw: Arc<Mutex<PixivWebClient>>, page: JsonValue, np: u16, progress_bars: Arc<MultiProgress>, datas: Arc<PixivData>, base: Arc<PathBuf>, helper: Arc<OptHelper>) -> i32 {
let link = &page["urls"]["original"];
if !link.is_string() {
println!("{}", gettext("Failed to get original picture's link."));
return 1;
}
let link = link.as_str().unwrap();
let file_name = get_file_name_from_url(link);
if file_name.is_none() {
println!("{} {}", gettext("Failed to get file name from url:"), link);
return 1;
}
let file_name = file_name.unwrap();
let file_name = base.join(file_name);
if file_name.exists() {
match helper.overwrite() {
Some(overwrite) => {
if !overwrite {
#[cfg(feature = "exif")]
{
if helper.update_exif() {
if add_exifdata_to_image(&file_name, &datas, np).is_err() {
println!(
"{} {}",
gettext("Failed to add exif data to image:"),
file_name.to_str().unwrap_or("(null)")
);
}
}
}
return 0;
}
}
None => {
if !ask_need_overwrite(file_name.to_str().unwrap()) {
return 0;
}
}
}
}
let r;
{
let mut pw = pw.lock().await;
r = pw.adownload_image(link).await;
if r.is_none() {
println!("{} {}", gettext("Failed to download image:"), link);
return 1;
}
}
let r = r.unwrap();
let re = WebClient::adownload_stream(&file_name, r, &helper, Some(progress_bars)).await;
if re.is_err() {
println!("{} {}", gettext("Failed to download image:"), link);
return 1;
}
#[cfg(feature = "exif")]
{
if add_exifdata_to_image(&file_name, &datas, np).is_err() {
println!(
"{} {}",
gettext("Failed to add exif data to image:"),
file_name.to_str().unwrap_or("(null)")
);
}
}
0
}
pub fn download_artwork(&self, pw: Arc<Mutex<PixivWebClient>>, id: u64) -> i32 {
let mut re = None;
let pages;
let mut ajax_ver = true;
if pw.helper.use_webpage() {
re = pw.get_artwork(id);
let helper = Arc::new(pw.try_lock().unwrap().helper.clone());
if helper.use_webpage() {
re = pw.try_lock().unwrap().get_artwork(id);
if re.is_some() {
ajax_ver = false;
}
}
if re.is_none() {
re = pw.get_artwork_ajax(id);
re = pw.try_lock().unwrap().get_artwork_ajax(id);
}
if re.is_none() {
return 1;
@@ -76,21 +150,22 @@ impl Main {
let pages = pages.unwrap();
let mut pages_data: Option<JsonValue> = None;
if pages > 1 {
pages_data = pw.get_illust_pages(id);
pages_data = pw.try_lock().unwrap().get_illust_pages(id);
}
if pages > 1 && pages_data.is_none() {
println!("{}", gettext("Failed to get pages' data."));
return 1;
}
let base = PathBuf::from(".");
let base = Arc::new(PathBuf::from("."));
let json_file = base.join(format!("{}.json", id));
let mut datas = PixivData::new(id, pw.helper.clone()).unwrap();
let mut datas = PixivData::new(id, (*helper).clone()).unwrap();
if ajax_ver {
datas.from_web_page_ajax_data(&re, true);
} else {
datas.from_web_page_data(&re, true);
}
let json_data = JSONDataFile::from(&datas);
let datas = Arc::new(datas);
let json_data = JSONDataFile::from(Arc::clone(&datas));
if !json_data.save(&json_file) {
println!("{}", gettext("Failed to save metadata to JSON file."));
return 1;
@@ -105,7 +180,7 @@ impl Main {
match illust_type {
0 => { }
2 => {
let ugoira_data = pw.get_ugoira(id);
let ugoira_data = pw.try_lock().unwrap().get_ugoira(id);
if ugoira_data.is_none() {
println!("{}", gettext("Failed to get ugoira's data."));
return 1;
@@ -125,7 +200,7 @@ impl Main {
let file_name = file_name.unwrap();
let file_name = base.join(file_name);
let dw = if file_name.exists() {
match pw.helper.overwrite() {
match helper.overwrite() {
Some(overwrite) => { overwrite }
None => { ask_need_overwrite(file_name.to_str().unwrap()) }
}
@@ -133,13 +208,13 @@ impl Main {
true
};
if dw {
let r = pw.download_image(src);
let r = pw.try_lock().unwrap().download_image(src);
if r.is_none() {
println!("{} {}", gettext("Failed to download ugoira:"), src);
return 1;
}
let r = r.unwrap();
let re = WebClient::download_stream(&file_name, r, &pw.helper);
let re = WebClient::download_stream(&file_name, r, &helper);
if re.is_err() {
println!("{} {}", gettext("Failed to download ugoira:"), src);
return 1;
@@ -153,7 +228,7 @@ impl Main {
}
#[cfg(feature = "ugoira")]
{
let metadata = match get_video_metadata(&datas) {
let metadata = match get_video_metadata(Arc::clone(&datas).as_ref()) {
Ok(m) => { m }
Err(e) => {
println!("{} {}", gettext("Warning: Failed to generate video's metadata:"), e);
@@ -184,7 +259,24 @@ impl Main {
} else {
println!("{}", gettext("Warning: Failed to get illust's type."));
}
if pages_data.is_some() {
if pages_data.is_some() && helper.download_multiple_images() {
let mut np = 0u16;
let pages_data = pages_data.as_ref().unwrap();
let progress_bars = Arc::new(MultiProgress::new());
let mut tasks = Vec::new();
for page in pages_data.members() {
let f = tokio::spawn(Self::download_artwork_page(Arc::clone(&pw), page.clone(), np, Arc::clone(&progress_bars), Arc::clone(&datas), Arc::clone(&base), Arc::clone(&helper)));
tasks.push(f);
np += 1;
}
let mut re = 0;
for task in tasks {
let r = spin_on(task);
re |= r.unwrap_or(1);
}
return re;
}
else if pages_data.is_some() {
#[cfg(feature = "exif")]
let mut np = 0u16;
let pages_data = pages_data.as_ref().unwrap();
@@ -203,12 +295,12 @@ impl Main {
let file_name = file_name.unwrap();
let file_name = base.join(file_name);
if file_name.exists() {
match pw.helper.overwrite() {
match helper.overwrite() {
Some(overwrite) => {
if !overwrite {
#[cfg(feature = "exif")]
{
if pw.helper.update_exif() {
if helper.update_exif() {
if add_exifdata_to_image(&file_name, &datas, np).is_err() {
println!(
"{} {}",
@@ -229,13 +321,13 @@ impl Main {
}
}
}
let r = pw.download_image(link);
let r = pw.try_lock().unwrap().download_image(link);
if r.is_none() {
println!("{} {}", gettext("Failed to download image:"), link);
return 1;
}
let r = r.unwrap();
let re = WebClient::download_stream(&file_name, r, &pw.helper);
let re = WebClient::download_stream(&file_name, r, &helper);
if re.is_err() {
println!("{} {}", gettext("Failed to download image:"), link);
return 1;
@@ -277,7 +369,7 @@ impl Main {
let file_name = file_name.unwrap();
let file_name = base.join(file_name);
if file_name.exists() {
let overwrite = match pw.helper.overwrite() {
let overwrite = match helper.overwrite() {
Some(overwrite) => {
overwrite
}
@@ -287,7 +379,7 @@ impl Main {
};
if !overwrite {
#[cfg(feature = "exif")]
if pw.helper.update_exif() {
if helper.update_exif() {
if add_exifdata_to_image(&file_name, &datas, 0).is_err() {
println!(
"{} {}",
@@ -299,13 +391,13 @@ impl Main {
return 0;
}
}
let r = pw.download_image(link);
let r = pw.try_lock().unwrap().download_image(link);
if r.is_none() {
println!("{} {}", gettext("Failed to download image:"), link);
return 1;
}
let r = r.unwrap();
let re = WebClient::download_stream(&file_name, r, &pw.helper);
let re = WebClient::download_stream(&file_name, r, &helper);
if re.is_err() {
println!("{} {}", gettext("Failed to download image:"), link);
return 1;

View File

@@ -11,17 +11,17 @@ use std::time::Duration;
/// An sturct to access all available settings/command line switches
#[derive(Clone, Debug)]
pub struct OptHelper<'a> {
pub struct OptHelper {
/// Command Line Options
opt: &'a CommandOpts,
opt: CommandOpts,
/// Settings
settings: &'a SettingStore,
settings: SettingStore,
default_retry_interval: NonTailList<Duration>,
_author_name_filters: Option<Vec<AuthorNameFilter>>,
_use_progress_bar: Option<UseProgressBar>,
}
impl<'a> OptHelper<'a> {
impl OptHelper {
pub fn author_name_filters(&self) -> Option<&Vec<AuthorNameFilter>> {
if self.settings.have("author-name-filters") {
return self._author_name_filters.as_ref();
@@ -51,7 +51,7 @@ impl<'a> OptHelper<'a> {
}
}
pub fn new(opt: &'a CommandOpts, settings: &'a SettingStore) -> Self {
pub fn new(opt: CommandOpts, settings: SettingStore) -> Self {
let mut l = NonTailList::default();
l += Duration::new(3, 0);
let _author_name_filters = if settings.have("author-name-filters") {
@@ -146,4 +146,15 @@ impl<'a> OptHelper<'a> {
}
String::from("[{elapsed_precise}] [{wide_bar:.green/yellow}] {bytes}/{total_bytes} ({bytes_per_sec}, {eta}) {msg:40}")
}
/// Return whether to download multiple images at the same time.
pub fn download_multiple_images(&self) -> bool {
if self.opt.download_multiple_images {
return true;
}
if self.settings.have_bool("download-multiple-images") {
return self.settings.get_bool("download-multiple-images").unwrap();
}
false
}
}

View File

@@ -36,7 +36,7 @@ impl PartialEq<ConfigCommand> for &ConfigCommand {
}
}
#[derive(Debug)]
#[derive(Clone, Debug)]
/// Command Line Options
pub struct CommandOpts {
/// Command
@@ -66,6 +66,8 @@ pub struct CommandOpts {
pub update_exif: bool,
/// Whether to enable progress bar
pub use_progress_bar: Option<UseOrNot>,
/// Whether to download multiple images at the same time
pub download_multiple_images: bool,
}
impl CommandOpts {
@@ -85,6 +87,7 @@ impl CommandOpts {
#[cfg(feature = "exif")]
update_exif: false,
use_progress_bar: None,
download_multiple_images: false,
}
}
@@ -180,6 +183,11 @@ pub fn parse_cmd() -> Option<CommandOpts> {
gettext("Whether to enable progress bar."),
"yes/no/auto",
);
opts.optflag(
"",
"download-multiple-images",
gettext("Download multiple images at the same time."),
);
let result = match opts.parse(&argv[1..]) {
Ok(m) => m,
Err(err) => {
@@ -300,5 +308,6 @@ pub fn parse_cmd() -> Option<CommandOpts> {
}
re.as_mut().unwrap().use_progress_bar = Some(r.unwrap());
}
re.as_mut().unwrap().download_multiple_images = result.opt_present("download-multiple-images");
re
}

View File

@@ -9,19 +9,19 @@ use reqwest::Response;
use spin_on::spin_on;
/// A client which use Pixiv's web API
pub struct PixivWebClient<'a> {
pub struct PixivWebClient {
client: WebClient,
pub helper: OptHelper<'a>,
pub helper: OptHelper,
/// true if in is initialized
inited: bool,
data: Option<JsonValue>,
}
impl<'a> PixivWebClient<'a> {
pub fn new(m: &'a Main) -> Self {
impl PixivWebClient {
pub fn new(m: &Main) -> Self {
Self {
client: WebClient::new(),
helper: OptHelper::new(m.cmd.as_ref().unwrap(), m.settings.as_ref().unwrap()),
helper: OptHelper::new(m.cmd.as_ref().unwrap().clone(), m.settings.as_ref().unwrap().clone()),
inited: false,
data: None,
}
@@ -156,6 +156,22 @@ impl<'a> PixivWebClient<'a> {
Some(r)
}
pub async fn adownload_image<U: IntoUrl + Clone>(&mut self, url: U) -> Option<Response> {
self.auto_init();
let r = self.client.aget(url, json::object!{"referer": "https://www.pixiv.net/"}).await;
if r.is_none() {
return None;
}
let r = r.unwrap();
let status = r.status();
let code = status.as_u16();
if code >= 400 {
println!("{} {}", gettext("Failed to download image:"), status);
return None;
}
Some(r)
}
pub fn get_artwork_ajax(&mut self, id: u64) -> Option<JsonValue> {
self.auto_init();
let r = self.client.get(format!("https://www.pixiv.net/ajax/illust/{}", id), None);
@@ -241,7 +257,7 @@ impl<'a> PixivWebClient<'a> {
}
}
impl <'a> Drop for PixivWebClient<'a> {
impl Drop for PixivWebClient {
fn drop(&mut self) {
if self.inited {
let c = self.helper.cookies();

View File

@@ -20,6 +20,7 @@ pub fn get_settings_list() -> Vec<SettingDes> {
SettingDes::new("update-exif", gettext("Add/Update exif information to image files even when overwrite are disabled."), JsonValueType::Boolean, None).unwrap(),
SettingDes::new("progress-bar-template", gettext("Progress bar's template. See <here> for more informations.").replace("<here>", "https://docs.rs/indicatif/latest/indicatif/#templates").as_str(), JsonValueType::Str, Some(check_nonempty_str)).unwrap(),
SettingDes::new("use-progress-bar", gettext("Whether to enable progress bar."), JsonValueType::Multiple, Some(check_user_or_not)).unwrap(),
SettingDes::new("download-multiple-images", gettext("Download multiple images at the same time."), JsonValueType::Boolean, None).unwrap(),
]
}

View File

@@ -6,6 +6,7 @@ use crate::gettext;
use crate::list::NonTailList;
use crate::opthelper::OptHelper;
use futures_util::StreamExt;
use indicatif::MultiProgress;
use indicatif::ProgressBar;
use indicatif::ProgressStyle;
use json::JsonValue;
@@ -18,6 +19,7 @@ use std::fs::File;
use std::fs::remove_file;
use std::io::Write;
use std::path::Path;
use std::sync::Arc;
use std::time::Duration;
pub trait ToHeaders {
@@ -231,9 +233,29 @@ impl WebClient {
None
}
pub async fn aget<U: IntoUrl + Clone, H: ToHeaders + Clone>(&mut self, url: U, headers: H) -> Option<Response> {
let mut count = 0u64;
while count <= self.retry {
let r = self._aget2(url.clone(), headers.clone()).await;
if r.is_some() {
return r;
}
count += 1;
if count <= self.retry {
let t = self.retry_interval.as_ref().unwrap()[(count - 1).try_into().unwrap()];
if !t.is_zero() {
println!("{}", gettext("Retry after <num> seconds.").replace("<num>", format!("{}", t.as_secs_f64()).as_str()).as_str());
tokio::time::sleep(t).await;
}
}
println!("{}", gettext("Retry <count> times now.").replace("<count>", format!("{}", count).as_str()).as_str());
}
None
}
/// Send GET requests
pub fn _get<U: IntoUrl, H: ToHeaders>(&mut self, url: U, headers: H) -> Option<Response> {
let r = self.aget(url, headers);
let r = self._aget(url, headers);
let r = r.send();
let r = spin_on(r);
match r {
@@ -251,7 +273,25 @@ impl WebClient {
Some(r)
}
pub fn aget<U: IntoUrl, H: ToHeaders>(&mut self, url: U, headers: H) -> RequestBuilder {
pub async fn _aget2<U: IntoUrl, H: ToHeaders>(&mut self, url: U, headers: H) -> Option<Response> {
let r = self._aget(url, headers);
let r = r.send().await;
match r {
Ok(_) => {}
Err(e) => {
println!("{} {}", gettext("Error when request:"), e);
return None;
}
}
let r = r.unwrap();
self.handle_set_cookie(&r);
if self.verbose {
println!("{}", r.status());
}
Some(r)
}
pub fn _aget<U: IntoUrl, H: ToHeaders>(&mut self, url: U, headers: H) -> RequestBuilder {
let s = url.as_str();
if self.verbose {
println!("GET {}", s);
@@ -274,6 +314,84 @@ impl WebClient {
r
}
pub async fn adownload_stream<S: AsRef<OsStr> + ?Sized>(file_name: &S, r: Response, opt: &OptHelper, progress_bars: Option<Arc<MultiProgress>>) -> Result<(), ()> {
let content_length = r.content_length();
let use_progress_bar = match &content_length {
Some(_) => { opt.use_progress_bar() }
None => { false }
};
let mut bar = if use_progress_bar {
Some(ProgressBar::new(content_length.unwrap()))
} else {
None
};
let p = Path::new(file_name);
if bar.is_some() {
bar.as_mut().unwrap().set_style(ProgressStyle::default_bar()
.template(opt.progress_bar_template().as_ref()).unwrap()
.progress_chars("#>-"));
let tmp = p.file_name().unwrap_or(p.as_os_str());
bar.as_mut().unwrap().set_message(gettext("Downloading \"<loc>\".").replace("<loc>", tmp.to_str().unwrap_or("<NULL>")));
if progress_bars.is_some() {
bar = Some(progress_bars.unwrap().add(bar.unwrap()));
}
}
if p.exists() {
let re = remove_file(p);
if re.is_err() {
if bar.is_none() {
println!("{} {}", gettext("Failed to remove file:"), re.unwrap_err());
} else {
bar.as_ref().unwrap().set_message(format!("{} {}", gettext("Failed to remove file:"), re.unwrap_err()));
bar.as_ref().unwrap().abandon();
}
return Err(());
}
}
let f = File::create(p);
if f.is_err() {
if bar.is_none() {
println!("{} {}", gettext("Failed to create file:"), f.unwrap_err());
} else {
bar.as_ref().unwrap().set_message(format!("{} {}", gettext("Failed to create file:"), f.unwrap_err()));
bar.as_ref().unwrap().abandon();
}
return Err(());
}
let mut f = f.unwrap();
let mut stream = r.bytes_stream();
while let Some(data) = stream.next().await {
if data.is_err() {
if bar.is_none() {
println!("{} {}", gettext("Error when downloading file:"), data.unwrap_err());
} else {
bar.as_ref().unwrap().set_message(format!("{} {}", gettext("Error when downloading file:"), data.unwrap_err()));
bar.as_ref().unwrap().abandon();
}
return Err(());
}
let data = data.unwrap();
if bar.is_some() {
bar.as_ref().unwrap().inc(data.len() as u64);
bar.as_ref().unwrap().tick();
}
let r = f.write(&data);
if r.is_err() {
if bar.is_none() {
println!("{} {}", gettext("Failed to write file:"), r.unwrap_err());
} else {
bar.as_ref().unwrap().set_message(format!("{} {}", gettext("Failed to write file:"), r.unwrap_err()));
bar.as_ref().unwrap().abandon();
}
return Err(());
}
}
if bar.is_some() {
bar.as_mut().unwrap().finish_with_message(format!("{} {}", gettext("Downloaded image:"), p.to_str().unwrap_or("(null)")));
}
Ok(())
}
/// Download a stream
/// * `file_name` - File name
/// * `r` - Response
@@ -291,7 +409,7 @@ impl WebClient {
};
if bar.is_some() {
bar.as_mut().unwrap().set_style(ProgressStyle::default_bar()
.template(opt.progress_bar_template().as_ref())
.template(opt.progress_bar_template().as_ref()).unwrap()
.progress_chars("#>-"));
}
let mut downloaded = 0usize;