Commit | Line | Data |
---|---|---|
64eb9fa5 FT |
1 | #!/usr/bin/python3 |
2 | ||
3 | import sys, os, getopt, time, random | |
4 | import manga.lib, manga.profile | |
5 | from PIL import Image | |
6 | ||
7 | verbose = 0 | |
8 | wait = 10 | |
e4aeea73 | 9 | getnames = False |
64eb9fa5 FT |
10 | |
11 | def msg(vl, msg, *args): | |
12 | if verbose >= vl: | |
13 | sys.stderr.write("getmanga: " + (msg % args) + "\n") | |
14 | ||
15 | def getprop(nm, default=None): | |
6e3b8ae1 | 16 | if mprof and "dl-" + nm in mprof.props: |
64eb9fa5 FT |
17 | return mprof.props["dl-" + nm] |
18 | if nm in props: | |
19 | return props[nm] | |
20 | return default | |
21 | ||
22 | def digits(num): | |
23 | n, i = 10, 1 | |
24 | while True: | |
25 | if num < n: | |
26 | return i | |
27 | n, i = n * 10, i + 1 | |
28 | ||
29 | def autoname(page): | |
30 | ret = "" | |
e4aeea73 | 31 | inames = [] |
64eb9fa5 FT |
32 | for t, i in page.stack: |
33 | if ret: | |
e4aeea73 FT |
34 | inames.append((t, ret)) |
35 | if ret: | |
64eb9fa5 FT |
36 | ret += "-" |
37 | ret += "%0*i" % (digits(len(t) + 1), i + 1) | |
e4aeea73 | 38 | return ret, inames |
64eb9fa5 FT |
39 | |
40 | def expand(pattern, page): | |
41 | ret = "" | |
42 | si = 0 | |
43 | fp = 0 | |
e4aeea73 | 44 | inames = [] |
67983628 | 45 | stack = list(zip([t for t, i in page.stack], [t for t, i in page.stack[1:]] + [page], [i for t, i in page.stack])) |
64eb9fa5 FT |
46 | while True: |
47 | p = pattern.find('%', fp) | |
48 | if p < 0: | |
67983628 | 49 | if si < len(stack): |
64eb9fa5 | 50 | sys.stderr.write("getmanga: pattern %s did not match page %s\n" % |
67983628 | 51 | (pattern, "/".join(c.name for t, c, i in stack))) |
64eb9fa5 | 52 | sys.exit(1) |
e4aeea73 | 53 | return ret + pattern[fp:], inames |
64eb9fa5 | 54 | ret += pattern[fp:p] |
67983628 FT |
55 | m = pattern[p + 1:p + 2] |
56 | fp = p + 2 | |
64eb9fa5 FT |
57 | if m == "%": |
58 | ret += "%" | |
59 | else: | |
67983628 | 60 | if si >= len(stack): |
64eb9fa5 | 61 | sys.stderr.write("getmanga: pattern %s did not match page %s\n" % |
67983628 | 62 | (pattern, "/".join(c.name for t, c, i in stack))) |
64eb9fa5 | 63 | sys.exit(1) |
67983628 | 64 | t, ct, ti = stack[si] |
64eb9fa5 FT |
65 | si += 1 |
66 | if m == "i": | |
67 | ret += "%0*i" % (digits(len(t) + 1), ti + 1) | |
68 | elif m == "n": | |
67983628 | 69 | ret += ct.name |
64eb9fa5 | 70 | elif m == "d": |
67983628 | 71 | ret += ct.id |
64eb9fa5 | 72 | else: |
67983628 | 73 | sys.stderr.write("getmanga: %s: unknown specifier `%s'\n" % (pattern, m)) |
64eb9fa5 | 74 | sys.exit(1) |
e4aeea73 FT |
75 | if si < len(stack): |
76 | inames.append((ct, ret)) | |
77 | ||
78 | checkednames = None | |
79 | def checknames(tdir, names): | |
80 | global checkednames | |
81 | nmpath = os.path.join(tdir, "names") | |
82 | if checkednames is None: | |
83 | checkednames = {} | |
84 | if os.path.exists(nmpath): | |
85 | with open(nmpath) as fp: | |
86 | for line in fp: | |
87 | line = line.strip() | |
88 | p = line.find(' ') | |
89 | if p < 0: continue | |
90 | checkednames[line[:p]] = line[p + 1:] | |
91 | for t, prefix in names: | |
92 | if not prefix: continue | |
93 | if ' ' not in prefix and prefix not in checkednames: | |
94 | with manga.profile.txfile(nmpath, "w") as fp: | |
95 | if '\n' not in t.name: | |
96 | checkednames[prefix] = t.name | |
97 | msg(1, "adding name %s for %s" % (t.name, prefix)) | |
98 | else: | |
99 | checkednames[prefix] = "" | |
100 | sys.stderr.write("getmanga: warning: node names contains newlines: %r\n" % (t.name,)) | |
101 | for prefix, name in checkednames.items(): | |
102 | if not name: continue | |
103 | fp.write("%s %s\n" % (prefix, name)) | |
64eb9fa5 FT |
104 | |
105 | def download(mng, tdir, pattern): | |
106 | exts = ["", ".jpg", ".jpeg", ".png", ".gif"] | |
107 | fmts = {"PNG": "png", "JPEG": "jpeg", "GIF": "gif"} | |
108 | for page in manga.lib.cursor(mng): | |
109 | if pattern is None: | |
e4aeea73 | 110 | nm, inames = autoname(page) |
64eb9fa5 | 111 | else: |
e4aeea73 FT |
112 | nm, inames = expand(pattern, page) |
113 | if getnames: | |
114 | checknames(tdir, inames) | |
64eb9fa5 FT |
115 | path = os.path.join(tdir, nm) |
116 | if any(os.path.exists(path + ext) for ext in exts): | |
117 | msg(2, "%s exists, skipping", nm) | |
118 | continue | |
119 | msg(1, "getting %s...", nm) | |
120 | with page.open() as fp: | |
121 | with open(path, "wb") as out: | |
daaea5d1 FT |
122 | done = False |
123 | try: | |
124 | while True: | |
125 | data = fp.read(65536) | |
126 | if data == b"": | |
127 | done = True | |
128 | break | |
129 | out.write(data) | |
130 | finally: | |
131 | if not done: | |
132 | os.unlink(path) | |
64eb9fa5 FT |
133 | try: |
134 | img = Image.open(path) | |
135 | except OSError: | |
136 | fmt = None | |
137 | else: | |
138 | fmt = img.format | |
139 | if fmt not in fmts: | |
140 | sys.stderr.write("getmanga: warning: could not determine file format of %s, leaving as is\n" % nm) | |
141 | else: | |
142 | os.rename(path, path + "." + fmts[fmt]) | |
143 | msg(3, "%s -> %s", nm, nm + "." + fmts[fmt]) | |
144 | cwait = abs(random.gauss(0, 1) * wait) | |
145 | msg(2, "waiting %.1f s...", cwait) | |
146 | time.sleep(cwait) | |
147 | ||
148 | def usage(out): | |
e4aeea73 | 149 | out.write("usage: getmanga [-hvn] [-w WAIT] [-p PROFILE] [-P PATTERN] DIRECTORY [LIBRARY ID]\n") |
aaab61ee FT |
150 | out.write("\tpattern templates:\n") |
151 | out.write("\t %i\tSequence number\n") | |
152 | out.write("\t %n\tName\n") | |
153 | out.write("\t %d\tID\n") | |
64eb9fa5 FT |
154 | |
155 | def main(): | |
e4aeea73 | 156 | global verbose, wait, mprof, props, getnames |
64eb9fa5 | 157 | |
e4aeea73 | 158 | opts, args = getopt.getopt(sys.argv[1:], "hvnp:w:P:") |
6e3b8ae1 | 159 | profnm = None |
da9e5bf6 | 160 | pattern = None |
64eb9fa5 FT |
161 | for o, a in opts: |
162 | if o == "-h": | |
163 | usage(sys.stdout) | |
164 | sys.exit(0) | |
165 | elif o == "-p": | |
166 | profnm = a | |
167 | elif o == "-v": | |
168 | verbose += 1 | |
e4aeea73 FT |
169 | elif o == "-n": |
170 | getnames = True | |
64eb9fa5 FT |
171 | elif o == "-w": |
172 | wait = int(a) | |
da9e5bf6 FT |
173 | elif o == "-P": |
174 | pattern = a | |
64eb9fa5 FT |
175 | if len(args) < 1: |
176 | usage(sys.stderr) | |
177 | sys.exit(1) | |
178 | tdir = args[0] | |
179 | ||
180 | if not os.path.isdir(tdir): | |
181 | sys.stderr.write("getmanga: %s: not a directory\n" % (tdir)) | |
182 | sys.exit(1) | |
183 | ||
184 | pfile = os.path.join(tdir, ".props") | |
185 | props = {} | |
186 | if os.path.exists(pfile): | |
187 | with open(pfile, "r") as fp: | |
66efacb2 | 188 | for words in manga.profile.splitlines(fp): |
64eb9fa5 FT |
189 | if words[0] == "set" and len(words) > 2: |
190 | props[words[1]] = words[2] | |
191 | elif words[0] == "lset" and len(words) > 1: | |
192 | props[words[1]] = words[2:] | |
193 | ||
6e3b8ae1 | 194 | if profnm is None: |
64eb9fa5 | 195 | profile = manga.profile.profile.last() |
6e3b8ae1 FT |
196 | elif profnm == "": |
197 | profile = None | |
64eb9fa5 FT |
198 | else: |
199 | profile = manga.profile.profile.byname(profnm) | |
200 | ||
201 | if len(args) == 2: | |
202 | usage(sys.stderr) | |
203 | sys.exit(1) | |
204 | elif len(args) > 2: | |
205 | libnm, mid = args[1:3] | |
206 | elif isinstance(props.get("manga"), list): | |
207 | libnm, mid = props["manga"] | |
208 | else: | |
209 | sys.stderr.write("getmanga: %s: id is neither saved nor given\n" % (tdir)) | |
210 | sys.exit(1) | |
211 | try: | |
212 | lib = manga.lib.findlib(libnm) | |
213 | except ImportError: | |
214 | sys.stderr.write("getmanga: no such library: %s\n" % (libnm)) | |
215 | sys.exit(1) | |
216 | try: | |
217 | mng = lib.byid(mid) | |
218 | except KeyError: | |
219 | sys.stderr.write("getmanga: no such manga: %s\n" % (mid)) | |
220 | sys.exit(1) | |
6e3b8ae1 FT |
221 | if profile is not None: |
222 | mprof = profile.getmanga(libnm, mng.id) | |
223 | else: | |
224 | mprof = None | |
64eb9fa5 | 225 | |
6e3b8ae1 | 226 | download(mng, tdir, pattern or getprop("pattern")) |
64eb9fa5 FT |
227 | |
228 | if __name__ == "__main__": | |
229 | try: | |
230 | main() | |
231 | except KeyboardInterrupt: | |
232 | pass |