]> git.draconx.ca Git - scripts.git/blob - caa-fetcher.py
Add script to convert "JWK"-format RSA keys to normal.
[scripts.git] / caa-fetcher.py
1 #!/usr/bin/env python
2 #
3 # Copyright © 2018-2019 Nick Bowler
4 #
5 # Tool to fetch album art for a release from the Cover Art Archive.
6 #
7 # License WTFPL2: Do What The Fuck You Want To Public License, version 2.
8 # This is free software: you are free to do what the fuck you want to.
9 # There is NO WARRANTY, to the extent permitted by law.
10
11 from pathlib import Path
12 import musicbrainzngs
13 import argparse
14 import tempfile
15 import urllib
16 import sys
17 import os
18 import re
19
20 umask = os.umask(0)
21 os.umask(umask)
22
23 progname = "caa-fetcher"
24 version = "1"
25 musicbrainzngs.set_useragent(progname, version)
26
27 parser = argparse.ArgumentParser(
28     description='Download album artwork from the Cover Art Archive'
29 )
30
31 def errmsg(msg, prog=parser.prog):
32     print("%s: %s" % (prog, msg), file=sys.stderr)
33
34 # NamedTemporaryFile workalike which allows control of the file mode...
35 def open_tmp(prefix="tmp", suffix="", dir=".", mode=0o600):
36     names = tempfile._get_candidate_names()
37     for seq in range(100):
38         name = os.path.join(dir, "%s%s%s" % (prefix, next(names), suffix))
39         try:
40             f = open(name, "x+", mode)
41         except FileExistsError:
42             continue
43         return tempfile._TemporaryFileWrapper(f, name)
44     return None
45
46 # Given an arbitrary string, return the first substring that looks like an
47 # mbid, or None if no such substring is found.
48 def extract_mbid(arg):
49     xdigit = r'[0-9abcdefABCDEF]'
50     m = re.search(r'{0}{{8}}(-{0}{{4}}){{3}}-{0}{{12}}'.format(xdigit), arg)
51     if m:
52         return m.group()
53
54 class VersionAction(argparse.Action):
55     def __init__(self, **kw):
56         super().__init__(nargs=0, help="show a version message and exit", **kw)
57     def __call__(self, parser, namespace, values, option_string=None):
58         print("%s %s" % (progname, version))
59         print('''Copyright © 2019 Nick Bowler
60 License WTFPL2: Do What The Fuck You Want To Public License, version 2.
61 This is free software: you are free to do what the fuck you want to.
62 There is NO WARRANTY, to the extent permitted by law.''')
63         sys.exit(0)
64 parser.add_argument('--version', action=VersionAction)
65
66 parser.add_argument('-o', '--output-directory', metavar='DIR',
67                     help='''downloaded files are written to DIR, which is
68                             created if it does not exists.  Default: .''')
69 parser.add_argument('-r', '--release-mbid', metavar='MBID', required=True)
70 args = parser.parse_args()
71
72 release_mbid = extract_mbid(args.release_mbid)
73 if release_mbid is None:
74     parser.error("invalid release MBID: %s" % (args.release_mbid))
75
76 # TODO: allow the naming scheme to be configured...
77 name_format = "{num:02d}"
78
79 try:
80     covers = musicbrainzngs.get_image_list(release_mbid)
81 except musicbrainzngs.ResponseError as e:
82     errmsg("error: cannot retrieve image list for release")
83     errmsg(e)
84     if isinstance(e.cause, urllib.error.HTTPError) and e.cause.code == 404:
85         errmsg("is %s a release MBID?" % (release_mbid))
86     sys.exit(1)
87
88 if len(covers["images"]) == 0:
89     print("release has no cover art, nothing to do")
90     sys.exit()
91
92 if args.output_directory:
93     Path(args.output_directory).mkdir(parents=True, exist_ok=True)
94     os.chdir(args.output_directory)
95
96 failed = False
97 for c in covers["images"]:
98     (_,ext) = os.path.splitext(c["image"])
99     outname = (name_format + "{0}").format(ext,
100         num = (covers["images"].index(c) + 1)
101     )
102
103     if Path(outname).exists():
104         print("%s already exists, skipping..." % outname)
105         continue
106
107     outfile = open_tmp(suffix=ext, dir=".", mode=0o666)
108     rc = os.spawnlp(os.P_WAIT, 'wget', 'wget', '-O', outfile.name, c["image"])
109     if rc:
110         failed = True
111
112     os.link(outfile.name, outname)
113     outfile.close()
114
115 if failed:
116     sys.exit(1)