A CLI tool for downloading from pixiv.net
修订版 | 46c78720d8de7460f4737b6e0443e6b565e8c3ed (tree) |
---|---|
时间 | 2023-09-29 19:04:43 |
作者 | mio <stigma@disr...> |
Commiter | mio |
Further improvements to the progress bar
Still can't say I'm completely happy with this (trailing newlines and
single-paged illustrations not changing the final message to 'Finished')
but it's an improvement.
I did spend a while trying to get a graphic progress bar, but couldn't
integrate the UI in a way that I liked (the current implementation looks
better IMO).
@@ -21,16 +21,17 @@ anything, has changed. | ||
21 | 21 | * All the help messages have been re-written to be more helpful and |
22 | 22 | provide examples. |
23 | 23 | * Images can be resumed if interrupted. |
24 | +* Progress bars used to display download progress. | |
24 | 25 | |
25 | 26 | ### Artist Command |
26 | 27 | |
27 | 28 | In the first version of pixiv_down, the `artist` command would only download |
28 | 29 | illustrations, requiring the use of `--type manga` if you wanted to download |
29 | -their manga as well. The new version of pixiv_down changes this so it will | |
30 | -download *both* illustrations and manga by default. | |
30 | +their manga as well. The new version of pixiv_down will download *both* | |
31 | +illustrations and manga by default. | |
31 | 32 | |
32 | -* Allow multiple artist IDs to consecutively download more than one artist | |
33 | - in one hit. | |
33 | +* Permits multiple artist IDs at once to consecutively download more than one | |
34 | + artist in a single run. | |
34 | 35 | * Download both illustrations and manga by default. |
35 | 36 | * Specify only one by using the `--type` option. |
36 | 37 |
@@ -53,4 +54,11 @@ your publicaly followed accounts. | ||
53 | 54 | * Removed `-l, --limit` option. It's highly probably this will return with a |
54 | 55 | different meaning. |
55 | 56 | |
57 | +### Bug Fixes | |
58 | + | |
59 | +* Using incorrect filename when downloading an Ugoira that uses Japanese in | |
60 | + the title. | |
61 | +* Incorrect behaviour when attempting to resume a previous manga download | |
62 | + that wasn't finished. | |
63 | + | |
56 | 64 | [Unreleased]: https://codeberg.org/supercell/pixiv_down/compare/v0.1...HEAD |
@@ -18,7 +18,7 @@ dependency "magickd:graphicsmagick_c" \ | ||
18 | 18 | |
19 | 19 | dependency "pixivd" \ |
20 | 20 | repository="git+https://pf.osdn.net/gitroot/n/ne/nemophila/pixivd.git" \ |
21 | - version="c9a28e4f8521840f3919c5a38817906e6be1fdef" | |
21 | + version="ec0f80a31846bc9a58dd1a31e583cbfebcfbc050" | |
22 | 22 | |
23 | 23 | ######################################################## |
24 | 24 | # If you are using the "fetch_dependencies.sh" script, # |
@@ -35,10 +35,6 @@ dependency "pixivd" \ | ||
35 | 35 | # version="*" |
36 | 36 | |
37 | 37 | |
38 | - | |
39 | - | |
40 | - | |
41 | - | |
42 | 38 | # Q16 default as that's what I use. Feel free to change |
43 | 39 | # this to suit yourself, just don't commit it. |
44 | 40 | configuration "Q16" { |
@@ -2,6 +2,6 @@ | ||
2 | 2 | "fileVersion": 1, |
3 | 3 | "versions": { |
4 | 4 | "magickd": {"version":"dd4985919d78d0cf37ab4b2ab99bb2ecec6aec15","repository":"git+https://repo.or.cz/magickd.git"}, |
5 | - "pixivd": {"version":"c9a28e4f8521840f3919c5a38817906e6be1fdef","repository":"git+https://pf.osdn.net/gitroot/n/ne/nemophila/pixivd.git"} | |
5 | + "pixivd": {"version":"ec0f80a31846bc9a58dd1a31e583cbfebcfbc050","repository":"git+https://pf.osdn.net/gitroot/n/ne/nemophila/pixivd.git"} | |
6 | 6 | } |
7 | 7 | } |
@@ -15,7 +15,7 @@ then | ||
15 | 15 | printf 'enum gitDate = "%s";\n' "$gdate" >> source/git_version.d |
16 | 16 | printf 'enum gitHash = "%s";\n' "$ghash" >> source/git_version.d |
17 | 17 | |
18 | - printf "[insert_gitversion] Success!\n" | |
18 | + printf "[prebuild_command] Successfully inserted git version.\n" | |
19 | 19 | fi |
20 | 20 | |
21 | 21 |
@@ -48,64 +48,97 @@ import pixivd.client; | ||
48 | 48 | |
49 | 49 | class ProgressMonitor |
50 | 50 | { |
51 | - import std.format : format; | |
52 | - import util : getTerminalColumns; | |
51 | + private size_t currentPage = 1; | |
52 | + private const size_t totalPages; | |
53 | 53 | |
54 | - size_t currentImage = 1; | |
54 | + this(size_t totalPages) | |
55 | + { | |
56 | + this.totalPages = totalPages; | |
57 | + } | |
55 | 58 | |
56 | 59 | void alreadyComplete(PageAlreadyDownloadedEvent ev) |
57 | 60 | { |
58 | 61 | import std.conv : ConvException, to; |
62 | + import std.format : format; | |
59 | 63 | |
60 | - size_t promptLength = 0; | |
61 | - | |
62 | - try { | |
63 | - const numberOfDigits = to!string(ev.totalPages).length; | |
64 | - const modifier = format("%%%dd", numberOfDigits); | |
65 | - const promptWM = format("\r Already downloaded page %s.", | |
66 | - modifier); | |
67 | - writefln(promptWM, currentImage); | |
68 | - } catch (ConvException ce) { | |
64 | + string value; | |
65 | + if (convertToString(ev.totalPages, value)) { | |
66 | + const numerOfDigits = value.length; | |
67 | + const modifier = format("%%%dd", numerOfDigits); | |
68 | + // ^ Determines the padding (e.g. %2d) | |
69 | + const prompt = format("\r Previously downloaded page %s/%d.", | |
70 | + modifier, ev.totalPages); | |
71 | + writefln(prompt, ev.currentPage); | |
72 | + } else { | |
69 | 73 | debug(app) |
70 | 74 | { |
71 | - stderr.writefln("ERORR converting size_t to string"); | |
72 | - stderr.writefln(" --> %s", ce.msg); | |
75 | + log.errorf("Converting size_t to string: %d", ev.totalPages); | |
73 | 76 | } |
74 | - writefln("\r Already downloaded page %d.", currentImage); | |
77 | + writefln("\r Previously downloaded page %d/%d.", | |
78 | + ev.currentPage, ev.totalPages); | |
75 | 79 | } |
76 | - currentImage += 1; | |
80 | + currentPage += 1; | |
77 | 81 | } |
78 | 82 | |
79 | 83 | void update(DownloadProgressEvent ev) |
80 | 84 | { |
81 | - import std.math.traits : isNaN, isInfinity; | |
85 | + import std.format : format; | |
86 | + import std.math : isNaN, isInfinity; | |
87 | + | |
88 | + string value; | |
89 | + string prompt; | |
90 | + if (convertToString(totalPages, value)) { | |
91 | + const modifier = format("%%%dd", value.length); | |
92 | + prompt = format("\r Downloading page %s/%d...", modifier, | |
93 | + totalPages); | |
94 | + prompt = format(prompt, currentPage); | |
95 | + } else { | |
96 | + prompt = format("\r Downloading page %d/%d..."); | |
97 | + } | |
98 | + | |
99 | + write(prompt); | |
82 | 100 | |
83 | 101 | if (ev.current == 0.0 || ev.total == 0.0) { |
84 | - write("\r 00.00%"); | |
102 | + write(" ( 0.00%)."); | |
85 | 103 | } else { |
86 | - double percentage = (cast(double)ev.current / ev.total) * 100; | |
104 | + const percentage = (cast(double)ev.current / ev.total) * 100.0; | |
87 | 105 | if (isNaN(percentage) || isInfinity(percentage)) { |
88 | - percentage = 0.0; | |
106 | + write(" ( 0.00%)."); | |
107 | + } else { | |
108 | + writef(" (%6.2f%%).", percentage); | |
89 | 109 | } |
90 | - writef("\r Downloading page %d... %6.2f%%", currentImage, | |
91 | - percentage); | |
92 | 110 | } |
93 | 111 | } |
94 | 112 | |
95 | 113 | void complete(DownloadCompleteEvent ev) |
96 | 114 | { |
115 | + import std.format : format; | |
116 | + import std.range : repeat; | |
117 | + import util : getTerminalColumns; | |
118 | + | |
119 | + string value; | |
120 | + | |
97 | 121 | if (ev.forPage) { |
98 | - string prompt = "\r Downloaded page %d.".format(currentImage); | |
99 | - size_t remLength = getTerminalColumns() - prompt.length; | |
122 | + if (convertToString(totalPages, value)) { | |
123 | + const modifier = format("%%%dd", value.length); | |
124 | + const prompt = format("\r Finished downloading page %s/%d.", | |
125 | + modifier, totalPages); | |
126 | + const remLength = getTerminalColumns() - prompt.length; | |
127 | + | |
128 | + writef(prompt, currentPage); | |
129 | + writefln("%s", ' '.repeat(remLength)); | |
130 | + } else { | |
131 | + const prompt = format("\r Finished downloading page %d/%d.", | |
132 | + currentPage, totalPages); | |
133 | + const remLength = getTerminalColumns() - prompt.length; | |
100 | 134 | |
101 | - write(prompt); | |
102 | - for (int i = 0; i < remLength; i++) { | |
103 | - write(' '); | |
135 | + write(prompt); | |
136 | + writefln("%s", ' '.repeat(remLength)); | |
104 | 137 | } |
105 | - | |
106 | - currentImage += 1; | |
138 | + currentPage += 1; | |
139 | + } else { | |
140 | + write("\n"); | |
107 | 141 | } |
108 | - write('\n'); | |
109 | 142 | } |
110 | 143 | } |
111 | 144 |
@@ -130,6 +163,28 @@ version(appimage) { | ||
130 | 163 | } |
131 | 164 | enum PixivDownVersionString = gitDate ~ " (" ~ gitHash ~ ")"; |
132 | 165 | |
166 | +/// | |
167 | +/// Converts *value* to a string *converted*. | |
168 | +/// | |
169 | +/// Params: | |
170 | +/// value = the value to convert | |
171 | +/// converted = the output that will hold the converted string. | |
172 | +/// | |
173 | +/// Returns: `true` if the conversion was successful, or `false` | |
174 | +/// if a ConvException was thrown. | |
175 | +/// | |
176 | +private bool convertToString(size_t value, out string converted) | |
177 | +{ | |
178 | + import std.conv : ConvException, to; | |
179 | + | |
180 | + try { | |
181 | + converted = to!string(value); | |
182 | + return true; | |
183 | + } catch (ConvException ce) { | |
184 | + return false; | |
185 | + } | |
186 | +} | |
187 | + | |
133 | 188 | /** |
134 | 189 | * Prompt for the PHPSESSID cookie. |
135 | 190 | * |
@@ -263,7 +318,7 @@ void downloadIllust(ref Illustration illust, ref Config conf, | ||
263 | 318 | illust.title); |
264 | 319 | log.tracef("Downloading post type %s", illust.type); |
265 | 320 | |
266 | - scope monitor = new ProgressMonitor(); | |
321 | + scope monitor = new ProgressMonitor(illust.pages); | |
267 | 322 | conf.client.connect(&monitor.alreadyComplete); |
268 | 323 | conf.client.connect(&monitor.update); |
269 | 324 | conf.client.connect(&monitor.complete); |
@@ -273,6 +328,7 @@ void downloadIllust(ref Illustration illust, ref Config conf, | ||
273 | 328 | } catch (FileException fe) { |
274 | 329 | log.warningf("FileException: %s (overwrite = %s)", fe.msg, |
275 | 330 | overwrite ? "true" : "false"); |
331 | + stderr.write("\r"); | |
276 | 332 | stderr.writefln("[%s] %s - %s has already been downloaded", illust.type, |
277 | 333 | illust.userName, illust.title); |
278 | 334 | } |
@@ -1,10 +1,31 @@ | ||
1 | +/* | |
2 | + * pixiv_down - CLI-based downloading tool for https://www.pixiv.net. | |
3 | + * Copyright (C) 2023 Mio | |
4 | + * | |
5 | + * This program is free software: you can redistribute it and/or modify | |
6 | + * it under the terms of the GNU General Public License as published by | |
7 | + * the Free Software Foundation, version 3 of the License. | |
8 | + * | |
9 | + * This program is distributed in the hope that it will be useful, | |
10 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | + * GNU General Public License for more details. | |
13 | + * | |
14 | + * You should have received a copy of the GNU General Public License | |
15 | + * along with this program. If not, see <https://www.gnu.org/licenses/>. | |
16 | + */ | |
1 | 17 | module util; |
2 | 18 | |
3 | -import std.conv; | |
4 | -import std.process; | |
5 | - | |
19 | +/// | |
20 | +/// Returns the number of columns in the terminal. | |
21 | +/// | |
22 | +/// Bugs: Only works on Posix. | |
23 | +/// | |
6 | 24 | ushort getTerminalColumns() |
7 | 25 | { |
26 | + import std.process : environment; | |
27 | + import std.conv : parse; | |
28 | + | |
8 | 29 | string columnsEnv = environment.get("COLUMNS"); |
9 | 30 | if (null is columnsEnv) { |
10 | 31 | return posixGetTerminalColumns(); |
@@ -12,13 +33,15 @@ ushort getTerminalColumns() | ||
12 | 33 | return parse!ushort(columnsEnv); |
13 | 34 | } |
14 | 35 | |
36 | +private: | |
37 | + | |
15 | 38 | version (Posix) |
16 | 39 | { |
17 | - import core.sys.posix.sys.ioctl; | |
18 | - import core.sys.posix.unistd; | |
19 | - | |
20 | 40 | ushort posixGetTerminalColumns() |
21 | 41 | { |
42 | + import core.sys.posix.sys.ioctl : winsize, TIOCGWINSZ, ioctl; | |
43 | + import core.sys.posix.unistd : STDOUT_FILENO; | |
44 | + | |
22 | 45 | winsize w; |
23 | 46 | ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); |
24 | 47 | return w.ws_col; |