Merge branch 'master' of git.dolda2000.com:/srv/git/r/automanga
[automanga.git] / getmanga
CommitLineData
64eb9fa5
FT
1#!/usr/bin/python3
2
3import sys, os, getopt, time, random
4import manga.lib, manga.profile
5from PIL import Image
6
7verbose = 0
8wait = 10
e4aeea73 9getnames = False
64eb9fa5
FT
10
11def msg(vl, msg, *args):
12 if verbose >= vl:
13 sys.stderr.write("getmanga: " + (msg % args) + "\n")
14
15def 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
22def 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
29def 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
40def 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
78checkednames = None
79def 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
105def 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
148def 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
155def 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
228if __name__ == "__main__":
229 try:
230 main()
231 except KeyboardInterrupt:
232 pass