Guest User

Untitled

a guest
Jan 19th, 2019
128
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 5.43 KB | None | 0 0
  1. from subprocess import check_output
  2. import json
  3. import requests
  4. from urllib.parse import urljoin
  5. from hashlib import md5
  6. from os import path
  7. import os
  8. import tempfile
  9. from urllib.request import urlretrieve
  10. import shutil
  11. from tqdm import tqdm
  12.  
  13. def read_config():
  14. with open('config.json') as f:
  15. return json.load(f)
  16.  
  17. CONFIG = read_config()
  18.  
  19. def get_user_dir(name=None):
  20. CMD = 'xdg-user-dir'
  21. return check_output((CMD,) if name is None else (CMD, name))[:-1].decode()
  22.  
  23. try:
  24. MUSIC_DIR = CONFIG['music_dir']
  25. except KeyError:
  26. MUSIC_DIR = get_user_dir('MUSIC')
  27.  
  28. class RelativeSession(requests.Session):
  29. def __init__(self, base_url):
  30. super(RelativeSession, self).__init__()
  31. self.__base_url = base_url
  32.  
  33. def request(self, method, url, **kwargs):
  34. url = urljoin(self.__base_url, url)
  35. return super(RelativeSession, self).request(method, url, **kwargs)
  36.  
  37. SERVER_ADDRESS = CONFIG['server_address']
  38. SESSION = RelativeSession(SERVER_ADDRESS)
  39.  
  40. def login():
  41. USER = CONFIG['user']
  42. PHONE = USER['phone']
  43. PASSWORD = USER['password']
  44. r = SESSION.get('/login/cellphone', params={
  45. 'phone': PHONE, 'password': PASSWORD
  46. })
  47. r.raise_for_status()
  48. return r.json()['account']['id']
  49.  
  50. def get_playlists(uid):
  51. r = SESSION.get('/user/playlist', params={
  52. 'uid': uid
  53. })
  54. r.raise_for_status()
  55. o = r.json()
  56. return [pl['id'] for pl in o['playlist']]
  57.  
  58. def get_playlist_detail(plid):
  59. r = SESSION.get('/playlist/detail', params={
  60. 'id': plid
  61. })
  62. r.raise_for_status()
  63. o = r.json()
  64. pl = o['playlist']
  65. return {
  66. 'name': pl['name'],
  67. 'tracks': [{
  68. 'id': track['id'],
  69. 'name': track['name']
  70. } for track in pl['tracks']]
  71. }
  72.  
  73. def get_recommand_playlist():
  74. r = SESSION.get('/recommend/songs')
  75. r.raise_for_status()
  76. o = r.json()
  77. return {
  78. 'name': '每日歌曲推荐',
  79. 'tracks': [{
  80. 'id': track['id'],
  81. 'name': track['name']
  82. } for track in o['recommend']]
  83. }
  84.  
  85. def get_song_download_infos(snids):
  86. r = SESSION.get('/song/url', params={
  87. 'id': ','.join(map(str, snids))
  88. })
  89. r.raise_for_status()
  90. o = r.json()
  91. return {
  92. d['id']: {
  93. 'url': d['url'],
  94. 'md5': d['md5'].lower(),
  95. 'type': d['type']
  96. } for d in o['data'] if d['url']
  97. }
  98.  
  99. def filemd5(ph):
  100. m = md5()
  101. with open(ph, 'rb') as f:
  102. for chunk in iter(lambda: f.read(4096), b""):
  103. m.update(chunk)
  104. return m.hexdigest()
  105.  
  106. def escape_filename(fn):
  107. return fn.replace('/', '\ufffd')
  108.  
  109. class TqdmUpTo(tqdm):
  110. def update_to(self, b=1, bsize=1, tsize=None):
  111. if tsize is not None:
  112. self.total = tsize
  113. self.update(b * bsize - self.n)
  114.  
  115.  
  116. def main():
  117. uid = login()
  118. plids = get_playlists(uid)
  119. pls = [get_playlist_detail(plid) for plid in plids]
  120. pls.append(get_recommand_playlist())
  121. infos = get_song_download_infos(set([tr['id'] for pl in pls for tr in pl['tracks']]))
  122. for pl in pls:
  123. for idx, tr in enumerate(pl['tracks']):
  124. try:
  125. tr.update(infos[tr['id']])
  126. except KeyError:
  127. pass
  128. tr['track_no'] = idx + 1
  129. ps_plnames = [d for d in os.listdir(MUSIC_DIR) if path.isdir(path.join(MUSIC_DIR, d))]
  130. with tempfile.TemporaryDirectory() as tmpdir:
  131. ps_md5s = set()
  132. for ps_pl in ps_plnames:
  133. d = path.join(MUSIC_DIR, ps_pl)
  134. for ps_tr in [s for s in os.listdir(d) if path.isfile(path.join(d, s))]:
  135. p = path.join(d, ps_tr)
  136. md5 = filemd5(p)
  137. try:
  138. os.link(p, path.join(tmpdir, md5))
  139. except FileExistsError:
  140. pass
  141. ps_md5s.add(md5)
  142. nds = {}
  143. for pl in pls:
  144. for tr in pl['tracks']:
  145. try:
  146. md5 = tr['md5']
  147. except KeyError:
  148. pass
  149. else:
  150. if md5 not in ps_md5s:
  151. nds[md5] = {
  152. 'url': tr['url'],
  153. 'name': tr['name']
  154. }
  155. err = None
  156. try:
  157. for md5, info in tqdm(nds.items(), unit='song'):
  158. tqdm.write('DOWNLOADING: {}'.format(info['name']))
  159. ph = path.join(tmpdir, md5)
  160. with TqdmUpTo(unit='B', unit_scale=True, miniters=1, leave=False) as t:
  161. (fn, _) = urlretrieve(info['url'], reporthook=t.update_to)
  162. if filemd5(fn) != md5:
  163. raise ValueError('hash mismatch')
  164. os.rename(fn, ph)
  165. except (KeyboardInterrupt, ValueError) as e:
  166. err = e
  167. for ps_pl in ps_plnames:
  168. d = path.join(MUSIC_DIR, ps_pl)
  169. shutil.rmtree(d)
  170. for pl in pls:
  171. d = path.join(MUSIC_DIR, escape_filename(pl['name']))
  172. os.mkdir(d)
  173. for tr in pl['tracks']:
  174. if 'md5' not in tr:
  175. continue
  176. fn = '{:02} - {}.{}'.format(tr['track_no'], tr['name'], tr['type'])
  177. fn = escape_filename(fn)
  178. try:
  179. os.link(path.join(tmpdir, tr['md5']), path.join(d, fn))
  180. except FileNotFoundError as e:
  181. if err is None:
  182. raise
  183. if err is not None:
  184. raise err
  185.  
  186. main()
Add Comment
Please, Sign In to add comment