diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..51cbe85 --- /dev/null +++ b/.gitignore @@ -0,0 +1,54 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +bin/ +build/ +develop-eggs/ +dist/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml + +# Translations +*.mo + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# Rope +.ropeproject + +# Django stuff: +*.log +*.pot + +# Sphinx documentation +docs/_build/ + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..75af56c --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 PeterDing + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..445be58 --- /dev/null +++ b/README.md @@ -0,0 +1,176 @@ +## iscript + +包含项目: + +- xiami.py - 下载或播放高品质虾米音乐(xiami.com) + +- pan.baidu.com.py - 百度网盘的下载和播放 + +- youku_parser.py - 下载或播放在线视频 + +- tumblr.py - 下载某个tumblr.com的所有图片 + +- unzip.py - 解决linux下unzip乱码的问题 + +- torrent2magnet.py - 种子转磁力 + +- 待续 + +--- + +--- + +### xiami.py - 下载或播放高品质虾米音乐(xiami.com) + +1. 依赖 + + python2-requests (https://github.com/kennethreitz/requests) + + python2-mutagen (https://code.google.com/p/mutagen/) + + mpv (http://mpv.io) + +2. 使用说明 + + 在源码中填入email和password后,可以将播放记录提交到虾米。 + + !!! vip账户支持高品质音乐的下载和播放。 + + 默认执行下载,如要播放,加参数-p。 + + 下载的MP3默认添加id3 tags,保存在当前目录下。 + + 日志保存在 ~/.Xiami.log, cookies保存在 ~/.Xiami.cookies。 + +3. 用法 + + \# xm 是xiami.py的马甲 (alias xm='python2 /path/to/xiami.py') + + # 下载专辑 + xm http://www.xiami.com/album/168709?spm=a1z1s.6928801.1561534521.114.ShN6mD + + # 下载单曲 + xm http://www.xiami.com/song/2082998?spm=a1z1s.6659513.0.0.DT2j7T + + # 下载精选集 + xm http://www.xiami.com/song/showcollect/id/30374035?spm=a1z1s.3061701.6856305.16.fvh75t + + # 下载该艺术家所有专辑或top 20歌曲 + xm http://www.xiami.com/artist/23460?spm=a1z1s.6928801.1561534521.115.ShW08b + + # 下载用户的收藏 + xm http://www.xiami.com/u/141825?spm=a1z1s.3521917.0.0.zI0APP + + + 播放: + + # url 是上面的 + xm -p url + +--- + +### pan.baidu.com.py - 百度网盘的下载和播放 + +1. 依赖 + + wget, aria2 + + python2-requests (https://github.com/kennethreitz/requests) + + mpv (http://mpv.io) + + mplayer # 我的linux上mpv播放wmv出错,换用mplayer + +2. 使用说明 + + 在源码中填入百度账户username和password后,可以递归下载自己的网盘文件。 + + 分享的网盘连接中只支持单个文件的下载。 + + 下载工具默认为wget, 可用参数-a选用aria2 + + 对所有文件,默认执行下载,如要播放媒体文件,加参数-p。 + + 下载的文件,保存在当前目录下。 + + cookies保存在 ~/.bp.cookies + +3. 用法 + + \# bp 是pan.baidu.com.py的马甲 (alias bp='python2 /path/to/pan.badiu.com.py') + + # 下载自己网盘中的*单个文件* + bp http://pan.baidu.com/disk/home#dir/path=/path/to/filename + # or + bp path=/path/to/filename + + # 递归下载自己网盘中的*文件夹* + bp http://pan.baidu.com/disk/home#dir/path=/path/to/directory + # or + bp path=/path/to/directory + + # 下载别人分享的*单个文件* + bp http://pan.baidu.com/s/1o64pFnW + bp http://pan.baidu.com/share/link?shareid=1622654699&uk=1026372002&fid=2112674284 + + # 下载别人加密分享的*单个文件*,密码参数-s + bp -s vuej http://pan.baidu.com/s/1i3FVlw5 + + # 下载用aria2, url 是上面的 + bp -a url + bp -a -s [secret] url + + 播放 + + # url 是上面的 + bp -p url + bp -s [secret] -p url + +--- + +### tumblr.py - 下载某个tumblr.com的所有图片 + +1. 依赖 + + wget + + python2-requests (https://github.com/kennethreitz/requests) + +2. 使用说明 + + 使用前需用在 http://www.tumblr.com/oauth/apps 加入一个app,证实后得到api_key,再在源码中填入,完成后则可使用。 + + 默认开5个进程,如需改变用参数-p [num]。 + + 下载的文件,保存在当前目录下。 + +3. 用法 + + \# tm是tumblr.py的马甲 (alias vx='python2 /path/to/tumblr.py') + + tm http://sosuperawesome.tumblr.com/ + +--- + +### unzip.py - 解决linux下unzip乱码的问题 + +用法 + + unzip.py azipfile.zip + +--- + +### torrent2magnet.py - 种子转磁力 + +1. 依赖 + python3 + +2. 使用说明 + + 将一个目录下的所有torrent转换成magnet,并保存于当前目录的magnet_link文件中。 + +2. 用法 + + \# ttm是torrent2magnet.py的马甲 (alias vx='python2 /path/to/torrent2magnet.py') + + ttm /path/to/directory diff --git a/pan.baidu.com.py b/pan.baidu.com.py new file mode 100755 index 0000000..0398bab --- /dev/null +++ b/pan.baidu.com.py @@ -0,0 +1,493 @@ +#!/usr/bin/env python2 +# vim: set fileencoding=utf8 + +import os +import sys +import requests +import urllib +import json +import re +import time +import argparse +import random +import select + + +username = '' +password = '' + + +############################################################ +# wget exit status +wget_es = { + 0: "No problems occurred.", + 2: "User interference.", + 1<<8: "Generic error code.", + 2<<8: "Parse error - for instance, when parsing command-line optio.wgetrc \ + or .netrc...", + 3<<8: "File I/O error.", + 4<<8: "Network failure.", + 5<<8: "SSL verification failure.", + 6<<8: "Username/password authentication failure.", + 7<<8: "Protocol errors.", + 8<<8: "Server issued an error response." +} +############################################################ + +s = '\x1b[1;%dm%s\x1b[0m' # terminual color template + +cookie_file = os.path.join(os.path.expanduser('~'), '.bpp.cookie') + +headers = { + "Accept":"text/html,application/xhtml+xml,application/xml; \ + q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding":"text/html", + "Accept-Language":"en-US,en;q=0.8,zh-CN;q=0.6,zh;q=0.4,zh-TW;q=0.2", + "Content-Type":"application/x-www-form-urlencoded", + "Referer":"http://www.baidu.com/", + "User-Agent":"Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 \ + (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36" +} + +ss = requests.session() +ss.headers.update(headers) + +class panbaiducom_HOME(object): + def __init__(self, url=''): + self.path = self.get_path(url) + self.download = self.play if args.play else self.download + + def init(self): + if os.path.exists(cookie_file): + t = json.loads(open(cookie_file).read()) + ss.cookies.update(t) + if not self.check_login(): + self.login() + else: + self.login() + + def get_path(self, url): + url = urllib.unquote(url) + #print repr(url) + f = re.search(r'path=(.+?)(&|$)', url) + #print f.group(1) + #sys.exit() + if f: + return f.group(1) + else: + return '/' + + def getvcode(self, codestring): + url = 'https://passport.baidu.com/cgi-bin/genimage?'+codestring + r = requests.get(url).content + with open('vcode.gif','wb') as f: + f.write(r) + p = os.popen('feh vcode.gif') + ret = input('vcode?') + p.terminate() + return ret + + def check_login(self): + print s % (97, '\n -- check_login') + url = 'http://www.baidu.com/home/msg/data/personalcontent' + j = ss.get(url) + if 'errNo":"0' in j.text: + print s % (92, ' -- check_login success\n') + self.save_cookies() + return True + else: + print s % (91, ' -- check_login fail\n') + return False + + def login(self): + print s % (97, '\n -- login') + + # Check if we have to deal with verify codes + params = { + 'tpl': 'pp', + 'callback': 'bdPass.api.login._needCodestringCheckCallback', + 'index': 0, + 'logincheck': '', + 'time': 0, + 'username': username + } + + # Ask server + url = 'https://passport.baidu.com/v2/api/?logincheck' + r = ss.get(url, params=params) + # Callback for verify code if we need + codestring = r.text[r.text.index('(')+1:r.text.index(')')] + codestring = json.loads(codestring)['codestring'] + codestring = codestring if codestring != "null" else None + verifycode = (self.getvcode(codestring)) if codestring != None else "" + + # Now we'll do login + # Get token + ss.get('http://www.baidu.com') + t = ss.get('https://passport.baidu.com/v2/api/?getapi&class=login \ + &tpl=pp&tangram=false').text + token = re.search(r'login_token=\'(.+?)\'', t).group(1) + + # Construct post body + data = { + 'token': token, + 'ppui_logintime': '1600000', + 'charset':'utf-8', + 'codestring': codestring, + 'isPhone': 'false', + 'index': 0, + 'u': '', + 'safeflg': 0, + 'staticpage': 'http://www.baidu.com/cache/user/html/jump.html', + 'loginType': 1, + 'tpl': 'pp', + 'callback': 'parent.bd__pcbs__qvljue', + 'username': username, + 'password': password, + 'verifycode': verifycode, + 'mem_pass': 'on', + 'apiver': 'v3' + } + + # Post! + # XXX : do not handle errors + url = 'https://passport.baidu.com/v2/api/?login' + r = ss.post(url, data=data) + self.save_cookies() + print s % (92, ' -- login success\n') + + def save_cookies(self): + with open(cookie_file, 'w') as g: + g.write(json.dumps(ss.cookies.get_dict(), indent=4, \ + sort_keys=True)) + + def get_infos(self): + t = {'Referer':'http://pan.baidu.com/disk/home'} + ss.headers.update(t) + + params = { + "channel": "chunlei", + "clienttype": 0, + "web": 1, + "num": 10000, ## max amount of listed file at one page + "t": int(time.time()*1000), + "page": 1, + #"desc": 1, ## reversely + "order": "name", ## sort by name, or size, time + "_": int(time.time()*1000) + #"bdstoken": token + } + url = 'http://pan.baidu.com/api/list' + + dir_loop = [self.path] + base_dir = os.path.split(self.path)[0] + for d in dir_loop: + params['dir'] = d + j = ss.get(url, params=params).json() + if j['errno'] == 0 and j['list']: + if args.type_: + j['list'] = [x for x in j['list'] if x['isdir'] \ + or x['server_filename'][-len(args.type_):] \ + == unicode(args.type_)] + if args.from_: + j['list'] = j['list'][args.from_-1:] if args.from_ else j['list'] + for i in j['list']: + if i['isdir']: + dir_loop.append(i['path'].encode('utf8')) + else: + t = i['path'].encode('utf8') + t = t.replace(base_dir, '') + t = t[1:] if t[0] == '/' else t + t = os.path.join(os.getcwd(), t) + infos = { + 'file': t, + 'dir_': os.path.split(t)[0], + 'dlink': i['dlink'].encode('utf8'), + 'name': i['server_filename'].encode('utf8') + } + self.download(infos) + elif j['errno'] != 0: + print s % (91, ' error: get_infos') + sys.exit(0) + elif not j['list']: + self.path, server_filename = os.path.split(self.path) + params['dir'] = self.path + j = ss.get(url, params=params).json() + if j['errno'] == 0 and j['list']: + for i in j['list']: + if i['server_filename'].encode('utf8') == server_filename: + t = os.path.join(os.getcwd(), server_filename) + infos = { + 'file': t, + 'dir_': os.path.split(t)[0], + 'dlink': i['dlink'].encode('utf8'), + 'name': i['server_filename'].encode('utf8') + } + self.download(infos) + break + + @staticmethod + def download(infos): + ## make dirs + if not os.path.exists(infos['dir_']): + os.makedirs(infos['dir_']) + else: + if os.path.exists(infos['file']): + return 0 + + num = random.randint(0, 7) % 7 + col = s % (num + 90, infos['file']) + print '\n ++ 正在下载: %s' % col + + if args.aria2c: + if args.limit: + cmd = 'aria2c -c -x10 -s10 \ + --max-download-limit %s \ + -o "%s.tmp" -d "%s" \ + --user-agent "%s" \ + --header "Referer:http://pan.baidu.com/disk/home" \ + "%s"' % (args.limit, infos['name'], infos['dir_'],\ + headers['User-Agent'], infos['dlink']) + else: + cmd = 'aria2c -c -x10 -s10 \ + -o "%s.tmp" -d "%s" --user-agent "%s" \ + --header "Referer:http://pan.baidu.com/disk/home" \ + "%s"' % (infos['name'], infos['dir_'], headers['User-Agent'], \ + infos['dlink']) + else: + #cmd = 'wget -c -O "%s.tmp" --user-agent "%s" --header "Referer:http://pan.baidu.com/disk/home" "%s"' % (infos['file'], headers['User-Agent'], infos['dlink']) + if args.limit: + cmd = 'wget -c --limit-rate %s \ + -O "%s.tmp" --user-agent "%s" \ + --header "Referer:http://pan.baidu.com/disk/home" "%s"' % \ + (args.limit, infos['file'], headers['User-Agent'], infos['dlink']) + else: + cmd = 'wget -c -O "%s.tmp" --user-agent "%s" \ + --header "Referer:http://pan.baidu.com/disk/home" "%s"' % \ + (infos['file'], headers['User-Agent'], infos['dlink']) + + status = os.system(cmd) + if status != 0: # other http-errors, such as 302. + wget_exit_status_info = wget_es[status] + print('\n\n ----### \x1b[1;91mERROR\x1b[0m ==> \ + \x1b[1;91m%d (%s)\x1b[0m ###--- \n\n' % \ + (status, wget_exit_status_info)) + print s % (91, ' ===> '), cmd + sys.exit(1) + else: + os.rename('%s.tmp' % infos['file'], infos['file']) + + @staticmethod + def play(infos): + num = random.randint(0, 7) % 7 + col = s % (num + 90, infos['name']) + print '\n ++ play: %s' % col + + if os.path.splitext(infos['file'])[-1].lower() == '.wmv': + cmd = 'mplayer -really-quiet -cache 8140 \ + -http-header-fields "user-agent:%s" \ + -http-header-fields "Referer:http://pan.baidu.com/disk/home" "%s"' % \ + (headers['User-Agent'], infos['dlink']) + else: + cmd = 'mpv --really-quiet --cache 8140 --cache-default 8140 \ + --http-header-fields "user-agent:%s" \ + --http-header-fields "Referer:http://pan.baidu.com/disk/home" "%s"' % \ + (headers['User-Agent'], infos['dlink']) + + status = os.system(cmd) + timeout = 1 + ii, _, _ = select.select([sys.stdin], [], [], timeout) + if ii: + sys.exit(0) + else: + pass + + def exists(self, filepath): + url = 'http://pan.baidu.com/api/filemanager' + + p = { + "channel": "chunlei", + "clienttype": 0, + "web": 1, + "opera": "rename" + } + + data = '[{"path": "%s", "newname": "%s"}]' % (filepath, os.path.split(filepath)[-1]) + data = 'filelist=' + urllib.quote_plus(data) + + r = ss.post(url, params=p, data=data, verify=False) + if r.ok: + if r.json()['errno']: + return False + else: + print s % (91, ' !! Error at exists') + + def upload(self, path, dir_): + url = 'https://c.pcs.baidu.com/rest/2.0/pcs/file' + path = os.path.expanduser(path) + + p = { + "method": "upload", + "app_id": "250528", + "ondup": "overwrite", + "dir": dir_, + "filename": os.path.split(path)[-1], + "BDUSS": ss.cookies['BDUSS'], + } + + files = {'file': ('file', open(path, 'rb'), '')} + + data = MultipartEncoder(files) + + theaders = headers + theaders['Content-Type'] = data.content_type + + r = ss.post(url, params=p, data=data, verify=False, headers=theaders) + if r.ok: + print s % (92, ' ++ upload success,'), "path:", s % (97, r.json()['path'].encode('utf8')) + else: + print s % (91, ' !! Error at upload') + + def do(self): + self.get_infos() + #self.exists('/aa/a') + +class panbaiducom(object): + def __init__(self, url): + self.url = url + self.secret = args.secret + self.infos = {} + + def secret_or_not(self): + r = ss.get(self.url) + if 'init' in r.url: + if not self.secret: + self.secret = raw_input(s % (92, " 请输入提取密码: ")) + data = 'pwd=%s' % self.secret + url = "%s&t=%d" % (r.url.replace('init', 'verify'), int(time.time())) + r = ss.post(url, data=data) + if r.json()['errno']: + print s % (91, " !! 提取密码错误\n") + sys.exit(1) + + def get_params(self): + r = ss.get(self.url) + pattern = re.compile('server_filename="(.+?)";disk.util.ViewShareUtils.bdstoken="(\w+)";' + 'disk.util.ViewShareUtils.fsId="(\d+)".+?FileUtils.share_uk="(\d+)";' + 'FileUtils.share_id="(\d+)";.+?FileUtils.share_timestamp="(\d+)";' + 'FileUtils.share_sign="(\w+)";') + p = re.search(pattern, r.text) + + self.params = { + "bdstoken": p.group(2), + "uk": p.group(4), + "shareid": p.group(5), + "timestamp": p.group(6), + "sign": p.group(7), + "channel": "chunlei", + "clienttype": 0, + "web": 1, + "channel": "chunlei", + "clienttype": 0, + "web": 1 + } + + self.infos.update({ + 'name': p.group(1).encode('utf8'), + 'file': os.path.join(os.getcwd(), p.group(1)).encode('utf8'), + 'dir_': os.getcwd(), + 'fs_id': p.group(3).encode('utf8') + }) + + def get_infos(self): + url = 'http://pan.baidu.com/share/download' + data = 'fid_list=["%s"]' % self.infos['fs_id'] + + while True: + r = ss.post(url, data=data, params=self.params) + j = r.json() + if not j['errno']: + self.infos['dlink'] = j['dlink'].encode('utf8') + if args.play: + panbaiducom_HOME.play(self.infos) + else: + panbaiducom_HOME.download(self.infos) + break + else: + vcode = j['vcode'] + self.save_img(j['img']) + input_code = raw_input(s % (92, " 请输入看到的验证码: ")) + self.params.update({'input': input_code, 'vcode': vcode}) + + def save_img(self, url): + path = os.path.join(os.path.expanduser('~'), 'vcode.jpg') + with open(path, 'w') as g: + data = urllib.urlopen(url).read() + g.write(data) + print " ++ 验证码已经保存至", s % (91, path) + + def get_infos2(self): + url = self.url + + while True: + r = ss.get(url) + j = r.content.replace('\\', '') + name = re.search(r'server_filename":"(.+?)"', j).group(1) + dlink = re.search(r'dlink":"(.+?)"', j) + if dlink: + self.infos = { + 'name': name, + 'file': os.path.join(os.getcwd(), name), + 'dir_': os.getcwd(), + 'dlink': dlink.group(1) + } + if args.play: + panbaiducom_HOME.play(self.infos) + else: + panbaiducom_HOME.download(self.infos) + break + else: + print s % (' !! Error at get_infos2, can\'t get dlink') + + def do(self): + self.secret_or_not() + self.get_params() + self.get_infos() + + def do2(self): + self.get_infos2() + +def main(url): + url = url.replace('wap/link', 'share/link') + if '/disk/' in url or 'path' in url: + x = panbaiducom_HOME(url) + x.init() + x.do() + elif 'baidu.com/pcloud/album/file' in url: + x = panbaiducom(url) + x.do2() + elif 'yun.baidu.com' in url or 'pan.baidu.com' in url: + x = panbaiducom(url) + x.do() + else: + print s % (91, ' !!! url 地址不正确.') + +if __name__ == '__main__': + p = argparse.ArgumentParser(description='download from pan.baidu.com') + p.add_argument('url', help='eg: http://pan.baidu.com/s/1gdutU3S, \ + http://pan.baidu.com/disk/home# \ + dir/path=/tmp/\xe5\x90\x8d\xe4\xbe\xa6\xe6\x8e\xa2\xe6\x9f\xaf\xe5\x8d\x97') + p.add_argument('-a', '--aria2c', action='store_true', \ + help='download with aria2c') + p.add_argument('-p', '--play', action='store_true', \ + help='play with mpv') + p.add_argument('-s', '--secret', action='store', \ + default=None, help='提取密码') + p.add_argument('-f', '--from_', action='store', \ + default=None, type=int, help='start at') + p.add_argument('-t', '--type_', action='store', \ + default=None, type=str, help='file\'s type') + p.add_argument('-l', '--limit', action='store', \ + default=None, type=str, help='limitrate') + args = p.parse_args() + main(args.url) diff --git a/torrent2magnet.py b/torrent2magnet.py new file mode 100755 index 0000000..62b2663 --- /dev/null +++ b/torrent2magnet.py @@ -0,0 +1,158 @@ +#! /usr/bin/python3 + +import sys, os, hashlib, urllib.parse, collections + +def bencode(elem): + if type(elem) == str: + elem = str.encode(elem) + if type(elem) == bytes: + result = str.encode(str(len(elem)))+b":"+elem + elif type(elem) == int: + result = str.encode("i"+str(elem)+"e") + elif type(elem) == list: + result = b"l" + for item in elem: + result += bencode(item) + result += b"e" + elif type(elem) in [dict, collections.OrderedDict]: + result = b"d" + for key in elem: + result += bencode(key)+bencode(elem[key]) + result += b"e" + return result + +def bdecode(bytestr, recursiveCall=False): + startingChars = dict({ + b"i" : int, + b":" : str, + b"l" : list, + b"d" : dict + }) + digits = [b"0", b"1", b"2", b"3", b"4", b"5", b"6", b"7", b"8", b"9"] + started = ended = False + curtype = None + numstring = b"" # for str, int + result = None # for list, dict + key = None # for dict + while len(bytestr) > 0: + # reading and popping from the beginning + char = bytestr[:1] + if not started: + bytestr = bytestr[1:] + if char in digits: + numstring += char + elif char in startingChars: + started = True + curtype = startingChars[char] + if curtype == str: + size = int(bytes.decode(numstring)) + # try to decode strings + try: + result = bytes.decode(bytestr[:size]) + except UnicodeDecodeError: + result = bytestr[:size] + bytestr = bytestr[size:] + ended = True + break + + elif curtype == list: + result = [] + elif curtype == dict: + result = collections.OrderedDict() + else: + raise ValueError("Expected starting char, got ‘"+bytes.decode(char)+"’") + else: # if started + if not char == b"e": + if curtype == int: + bytestr = bytestr[1:] + numstring += char + elif curtype == list: + item, bytestr = bdecode(bytestr, recursiveCall=True) + result.append(item) + elif curtype == dict: + if key == None: + key, bytestr = bdecode(bytestr, recursiveCall=True) + else: + result[key], bytestr = bdecode(bytestr, recursiveCall=True) + key = None + else: # ending: char == b"e" + bytestr = bytestr[1:] + if curtype == int: + result = int(bytes.decode(numstring)) + ended = True + break + if ended: + if recursiveCall: + return result, bytestr + else: + return result + else: + raise ValueError("String ended unexpectedly") + +def torrent2magnet(torrentdic, new_trackers=None): + result = [] + + # add hash info + if "info" not in torrentdic: + raise ValueError("No info dict in torrent file") + encodedInfo = bencode(torrentdic["info"]) + sha1 = hashlib.sha1(encodedInfo).hexdigest() + result.append("xt=urn:btih:"+sha1) + + # add display name + #if "name" in torrentdic["info"]: + #quoted = urllib.parse.quote(torrentdic["info"]["name"], safe="") + #result.append("dn="+quoted) + + # add trackers list + #trackers = [] + #if "announce-list" in torrentdic: + #for urllist in torrentdic["announce-list"]: + #trackers += urllist + #elif "announce" in torrentdic: + #trackers.append(torrentdic["announce"]) + #if new_trackers: + #trackers += new_trackers + + # eliminate duplicates without sorting + #seen_urls = [] + #for url in trackers: + #if [url] not in seen_urls: + #seen_urls.append([url]) + #quoted = urllib.parse.quote(url, safe="") + #result.append("tr="+quoted) + #torrentdic["announce-list"] = seen_urls + + # output magnet or torrent file + #if output == sys.stdout: + magnet_link = "magnet:?" + "&".join(result) + return magnet_link + #else: + #out = open(output, 'bw') + #out.write(bencode.bencode(torrentdic)) + #out.close() + +def writer(cwd, i): + with open(cwd, 'a') as g: + g.write(i + '\n\n') + +def main(directory): + directory = os.path.abspath(directory) + cwd = os.path.join(os.getcwd(), 'magnet_links') + for a, b, c in os.walk(directory): + for i in c: + file_ext = os.path.splitext(i)[-1] + if file_ext == '.torrent': + file_name = os.path.join(a, i) + byte_stream = open(file_name, 'br').read() + try: + torrentdic = bdecode(byte_stream) + magnet_link = torrent2magnet(torrentdic) + tt = '## ' + i + ':\n' + magnet_link + writer(cwd, tt) + except: + pass + +if __name__ == '__main__': + argv = sys.argv + main(argv[1]) diff --git a/tumblr.py b/tumblr.py new file mode 100755 index 0000000..06353cb --- /dev/null +++ b/tumblr.py @@ -0,0 +1,184 @@ +#!/usr/bin/env python2 +# vim: set fileencoding=utf8 + +import os, sys, re, json, requests, argparse, random, multiprocessing, time, subprocess + +api_key = '' + +############################################################ +# wget exit status +wget_es = { + 0: "No problems occurred.", + 2: "User interference.", + 1<<8: "Generic error code.", + 2<<8: "Parse error - for instance, when parsing command-line optio.wgetrc or .netrc...", + 3<<8: "File I/O error.", + 4<<8: "Network failure.", + 5<<8: "SSL verification failure.", + 6<<8: "Username/password authentication failure.", + 7<<8: "Protocol errors.", + 8<<8: "Server issued an error response." +} +############################################################ + +s = '\x1b[1;%dm%s\x1b[0m' # terminual color template + +headers = { + "Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding":"text/html", + "Accept-Language":"en-US,en;q=0.8,zh-CN;q=0.6,zh;q=0.4,zh-TW;q=0.2", + "Content-Type":"application/x-www-form-urlencoded", + "Referer":"https://api.tumblr.com/console//calls/blog/posts", + "User-Agent":"Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36" +} + +ss = requests.session() +ss.headers.update(headers) + +class tumblr(object): + def __init__(self, burl): + self.infos = {'host': re.search(r'http(s|)://(.+?)($|/)', burl).group(2)} + self.infos['dir_'] = os.path.join(os.getcwd(), self.infos['host']) + self.processes = int(args.processes) + + if not os.path.exists(self.infos['dir_']): + os.makedirs(self.infos['dir_']) + self.json_path = os.path.join(self.infos['dir_'], 'json.json') + self.offset = 0 + print s % (92, '\n ## begin'), 'offset = %s' % self.offset + else: + self.json_path = os.path.join(self.infos['dir_'], 'json.json') + if os.path.exists(self.json_path): + self.offset = json.loads(open(self.json_path).read())['offset'] - 20 + print s % (92, '\n ## begin'), 'offset = %s' % self.offset + else: + self.offset = 0 + + def save_json(self): + with open(self.json_path, 'w') as g: + g.write(json.dumps({'offset': self.offset}, indent=4, sort_keys=True)) + + def get_infos(self, postid=None): + self.infos['photos'] = [] + self.url = 'http://api.tumblr.com/v2/blog/%s/posts/photo' % self.infos['host'] + params = { + "offset": self.offset if not postid else "", + "limit": 20 if not postid else "", + "type": "photo", + "filter": "text", + #"id": postid if postid else "", + "api_key": api_key + } + + r = None + while True: + try: + r = ss.get(self.url, params=params, timeout=10) + break + except Exception as e: + print s % (91, ' !! Error, ss.get'), e + time.sleep(5) + if r.ok: + j = r.json() + if j['response']['posts']: + for i in j['response']['posts']: + index = 1 + for ii in i['photos']: + durl = ii['original_size']['url'].encode('utf8') + filepath = os.path.join(self.infos['dir_'], '%s_%s.%s' % (i['id'], index, durl.split('.')[-1])) + filename = os.path.split(filepath)[-1] + t = { + 'filepath': filepath, + 'durl': durl, + 'filename': filename + } + index += 1 + self.infos['photos'].append(t) + else: + print s % (92, '\n --- job over ---') + sys.exit(0) + else: + print s % (91, '\n !! Error, get_infos') + print r.status_code, r.content + sys.exit(1) + + def download(self): + def run(i): + #if not os.path.exists(i['filepath']): + num = random.randint(0, 7) % 7 + col = s % (num + 90, i['filepath']) + print '\n ++ 正在下载: %s' % col + + cmd = 'wget -c -T 4 -q -O "%s.tmp" --header "Referer: http://www.tumblr.com" --user-agent "%s" "%s"' % (i['filepath'], headers['User-Agent'], i['durl']) + + status = os.system(cmd) + if status != 0: # other http-errors, such as 302. + wget_exit_status_info = wget_es[status] + print('\n\n ----### \x1b[1;91mERROR\x1b[0m ==> \x1b[1;91m%d (%s)\x1b[0m ###--- \n\n' % (status, wget_exit_status_info)) + print s % (91, ' ===> '), cmd + sys.exit(1) + else: + os.rename('%s.tmp' % i['filepath'], i['filepath']) + + l = [self.infos['photos'][i:i+self.processes] for i in range(len(self.infos['photos']))[::self.processes]] + for yy in l: + ppool = [] + for ii in yy: + if not os.path.exists(ii['filepath']): + p = multiprocessing.Process(target=run, args=(ii,)) + p.start() + print p + ppool.append(p) + + for p in ppool: p.join() + + #print self.infos['photos'] + #sys.exit() + #pool = {i:None for i in range(self.processes)} + #for ii in self.infos['photos']: + #if not os.path.exists(ii['filepath']): + #is_on = False + #while not is_on: + #for n in pool: + #if not pool[n]: + #p = multiprocessing.Process(target=run, args=(ii,)) + #p.start() + #pool[n] = p + #is_on = True + #elif not pool[n].is_alive: + #pool[n].join() + #p = multiprocessing.Process(target=run, args=(ii,)) + #p.start() + #pool[n] = p + #is_on = True + #else: + #pass + #if not is_on: + #time.sleep(2) + #else: + #print 'pass' + + def do(self): + if args.check: + t = subprocess.check_output('ls "%s" | grep ".tmp"' % self.infos['dir_'], shell=True) + t = re.findall(r'\d\d\d+', t) + ltmp = list(set(t)) + for postid in ltmp: + self.get_infos(postid) + self.download() + else: + while True: + self.get_infos() + self.offset += 20 + self.save_json() + self.download() + +if __name__ == '__main__': + p = argparse.ArgumentParser(description='download from tumblr.com') + p.add_argument('url', help='url') + p.add_argument('-p', '--processes', action='store', default=5, help='amount of process') + p.add_argument('-c', '--check', action='store_true', help='fix reminded tmp') + args = p.parse_args() + url = args.url + x = tumblr(url) + x.do() diff --git a/unzip.py b/unzip.py new file mode 100755 index 0000000..6be59a9 --- /dev/null +++ b/unzip.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- + +import os +import sys +import zipfile + +print "Processing File " + sys.argv[1] + +file = '' +if len(sys.argv) == 3: + file=zipfile.ZipFile(sys.argv[1],"r") + file.setpassword(sys.argv[2]) +else: + file=zipfile.ZipFile(sys.argv[1],"r") + +for name in file.namelist(): + try: + utf8name=name.decode('gbk') + pathname = os.path.dirname(utf8name) + except: + utf8name=name + pathname = os.path.dirname(utf8name) + + print "Extracting " + utf8name + #pathname = os.path.dirname(utf8name) + if not os.path.exists(pathname) and pathname != "": + os.makedirs(pathname) + data = file.read(name) + if not os.path.exists(utf8name): + try: + fo = open(utf8name, "w") + fo.write(data) + fo.close + except: + pass +file.close() diff --git a/xiami.py b/xiami.py new file mode 100755 index 0000000..18385e0 --- /dev/null +++ b/xiami.py @@ -0,0 +1,653 @@ +#!/usr/bin/env python2 +# vim: set fileencoding=utf8 + +import re +import sys +import os +import random +import time +import json +import logging +import argparse +import requests +import urllib +import select +from mutagen.id3 import ID3,TRCK,TIT2,TALB,TPE1,APIC,TDRC,COMM,TPOS,USLT +from HTMLParser import HTMLParser + +parser = HTMLParser() +s = u'\x1b[1;%dm%s\x1b[0m' # terminual color template + + +email = '' # vip账号支持高品质音乐下载 +password = '' + + +############################################################# +# Xiami api for android +#{{{ +# url_action_fav = "http://www.xiami.com/app/android/fav?id=%s&type=%s" +# url_action_unfav = "http://www.xiami.com/app/android/unfav?id=%s&type=%s" +# url_album = "http://www.xiami.com/app/android/album?id=%s&uid=%s" +# url_song = "http://www.xiami.com/app/android/song?id=%s&uid=%s" +# url_artist = "http://www.xiami.com/app/android/artist?id=%s" +# url_artist_albums = "http://www.xiami.com/app/android/artist-albums?id=%s&page=%s" +# url_artist_radio = "http://www.xiami.com/app/android/radio-artist?id=%s" +# url_artist_top_song = "http://www.xiami.com/app/android/artist-topsongs?id=%s" +# url_artsit_similars = "http://www.xiami.com/app/android/artist-similar?id=%s" +# url_collect = "http://www.xiami.com/app/android/collect?id=%s&uid=%s" +# url_grade = "http://www.xiami.com/app/android/grade?id=%s&grade=%s" +# url_lib_albums = "http://www.xiami.com/app/android/lib-albums?uid=%s&page=%s" +# url_lib_artists = "http://www.xiami.com/app/android/lib-artists?uid=%s&page=%s" +# url_lib_collects = "http://www.xiami.com/app/android/lib-collects?uid=%s&page=%s" +# url_lib_songs = "http://www.xiami.com/app/android/lib-songs?uid=%s&page=%s" +# url_myplaylist = "http://www.xiami.com/app/android/myplaylist?uid=%s" +# url_myradiosongs = "http://www.xiami.com/app/android/lib-rnd?uid=%s" +# url_playlog = "http://www.xiami.com/app/android/playlog?id=%s&uid=%s" +# url_push_songs = "http://www.xiami.com/app/android/push-songs?uid=%s&deviceid=%s" +# url_radio = "http://www.xiami.com/app/android/radio?id=%s&uid=%s" +# url_radio_categories = "http://www.xiami.com/app/android/radio-category" +# url_radio_similar = "http://www.xiami.com/app/android/radio-similar?id=%s&uid=%s" +# url_rndsongs = "http://www.xiami.com/app/android/rnd?uid=%s" +# url_search_all = "http://www.xiami.com/app/android/searchv1?key=%s" +# url_search_parts = "http://www.xiami.com/app/android/search-part?key=%s&type=%s&page=%s" +#}}} +############################################################# + +############################################################ +# Xiami api for android +# {{{ +url_song = "http://www.xiami.com/app/android/song?id=%s" +url_album = "http://www.xiami.com/app/android/album?id=%s" +url_collect = "http://www.xiami.com/app/android/collect?id=%s" +url_artist_albums = "http://www.xiami.com/app/android/artist-albums?id=%s&page=%s" +url_artist_top_song = "http://www.xiami.com/app/android/artist-topsongs?id=%s" +url_lib_songs = "http://www.xiami.com/app/android/lib-songs?uid=%s&page=%s" +# }}} +############################################################ + +############################################################ +# wget exit status +wget_es = { + 0:"No problems occurred.", + 2:"User interference.", + 1<<8:"Generic error code.", + 2<<8:"Parse error - for instance, when parsing command-line \ + optio.wgetrc or .netrc...", + 3<<8:"File I/O error.", + 4<<8:"Network failure.", + 5<<8:"SSL verification failure.", + 6<<8:"Username/password authentication failure.", + 7<<8:"Protocol errors.", + 8<<8:"Server issued an error response." +} +############################################################ + +cookie_file = os.path.join(os.path.expanduser('~'), '.Xiami.cookies') + +headers = { + "Accept":"text/html,application/xhtml+xml,application/xml; \ + q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding":"text/html", + "Accept-Language":"en-US,en;q=0.8,zh-CN;q=0.6,zh;q=0.4,zh-TW;q=0.2", + "Content-Type":"application/x-www-form-urlencoded", + "Referer":"http://www.xiami.com/", + "User-Agent":"Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 \ + (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36" +} + +ss = requests.session() +ss.headers.update(headers) + +############################################################ +# Regular Expression Templates +re_disc_description = r'disc (\d+) \[(.+?)\]' +############################################################ + +def decry(row, encryed_url): + url = encryed_url + urllen = len(url) + rows = int(row) + + cols_base = urllen / rows # basic column count + rows_ex = urllen % rows # count of rows that have 1 more column + + matrix = [] + for r in xrange(rows): + length = cols_base + 1 if r < rows_ex else cols_base + matrix.append(url[:length]) + url = url[length:] + + url = '' + for i in xrange(urllen): + url += matrix[i % rows][i / rows] + + return urllib.unquote(url).replace('^', '0') + +def modificate_text(text): + text = parser.unescape(text) + text = re.sub(r'//*', '-', text) + text = text.replace('/', '-') + text = text.replace('\\', '-') + text = re.sub(r'\s\s+', ' ', text) + return text + +def modificate_file_name_for_wget(file_name): + file_name = re.sub(r'\s*:\s*', u' - ', file_name) # for FAT file system + file_name = file_name.replace('?', '') # for FAT file system + file_name = file_name.replace('"', '\'') # for FAT file system + return file_name + +def z_index(song_infos): + size = len(song_infos) + if size <= 9: + return 1 + elif size >= 10 and size <= 99: + return 2 + elif size >= 100 and size <= 999: + return 3 + else: + return 1 + +######################################################## + +class xiami(object): + def __init__(self, url): + self.email = email + self.password = password + self.url = url + self.song_infos = [] + self.json_url = '' + self.dir_ = os.getcwd().decode('utf8') + self.template_wgets = 'wget -c -T 5 -nv -U "%s" -O' % \ + headers['User-Agent'] + ' "%s.tmp" %s' + self.template_song = 'http://www.xiami.com/song/gethqsong/sid/%s' + self.template_record = 'http://www.xiami.com/count/playrecord?sid=%s' + + self.showcollect_id = '' + self.album_id = '' + self.artist_id = '' + self.song_id = '' + self.user_id = '' + self.cover_id = '' + self.cover_data = '' + + self.html = '' + self.disc_description_archives = {} + + self.download = self.play if args.play else self.download + + def init(self): + if os.path.exists(cookie_file): + t = json.loads(open(cookie_file).read()) + ss.cookies.update(t) + if not self.check_login(): + self.login() + else: + self.login() + + def check_login(self): + print s % (97, '\n -- check_login') + url = 'http://www.xiami.com/task/signin' + r = ss.get(url) + if r.content: + print s % (92, ' -- check_login success\n') + self.save_cookies() + return True + else: + print s % (91, ' -- login fail, please check email and password\n') + return False + + def login(self): + print s % (97, '\n -- login') + + validate = self.get_validate() + data = { + 'email': self.email, + 'password': self.password, + 'validate': validate, + 'remember': 1, + 'LoginButton': '登录' + } + + url = 'http://www.xiami.com/web/login' + r = ss.post(url, data=data) + self.check_login() + + def get_validate(self): + url = 'https://login.xiami.com/coop/checkcode?forlogin=1&%s' \ + % int(time.time()) + path = os.path.join(os.path.expanduser('~'), 'vcode.png') + with open(path, 'w') as g: + data = ss.get(url).content + g.write(data) + print " ++ 验证码已经保存至", s % (91, path) + print s % (92, u' 请输入验证码:') + validate = raw_input() + return validate + + def save_cookies(self): + with open(cookie_file, 'w') as g: + g.write(json.dumps(ss.cookies.get_dict(), indent=4, sort_keys=True)) + + def get_durl(self, id_): + while True: + try: + j = ss.get(self.template_song % id_).json() + t = j['location'] + row = t[0] + encryed_url = t[1:] + durl = decry(row, encryed_url) + return durl + except Exception as e: + print s % (91, ' \\\n \\-- Error, get_durl --'), e + time.sleep(5) + + def record(self, id_): + try: + api_json = self.opener.open(self.template_record % id_).read() + except: + pass + + def get_cover(self, info): + if info['album_name'] == self.cover_id: + return self.cover_data + else: + self.cover_id = info['album_name'] + while True: + url = info['album_pic_url'] + try: + self.cover_data = ss.get(url).content + if self.cover_data[:5] != '> 输入 a 下载该艺术家所有专辑.\n \ + >> 输入 t 下载该艺术家top 20歌曲.\n >> ') + if code == 'a': + print(s % (92, u'\n -- 正在分析艺术家专辑信息 ...')) + self.download_artist_albums() + elif code == 't': + print(s % (92, u'\n -- 正在分析艺术家top20信息 ...')) + self.download_artist_top_20_songs() + else: + print(s % (92, u' --> Over')) + elif '/song/' in self.url: + self.song_id = re.search(r'/song/(\d+)', self.url).group(1) + print(s % (92, u'\n -- 正在分析歌曲信息 ...')) + self.download_song() + elif '/u/' in self.url: + self.user_id = re.search(r'/u/(\d+)', self.url).group(1) + print(s % (92, u'\n -- 正在分析用户歌曲库信息 ...')) + self.download_user_songs() + else: + print(s % (91, u' 请正确输入虾米网址.')) + + def get_song_info(self, album_description, z, cd_serial_auth, i): + song_info = {} + song_info['song_id'] = i['song_id'] + song_info['song_url'] = u'http://www.xiami.com/song/' + i['song_id'] + song_info['track'] = i['track'] + song_info['album_description'] = album_description + #song_info['lyric_url'] = i['lyric'] + #song_info['sub_title'] = i['sub_title'] + #song_info['composer'] = i['composer'] + #song_info['disc_code'] = i['disc_code'] + #if not song_info['sub_title']: song_info['sub_title'] = u'' + #if not song_info['composer']: song_info['composer'] = u'' + #if not song_info['disc_code']: song_info['disc_code'] = u'' + t = time.gmtime(int(i['gmt_publish'])) + #song_info['year'] = unicode('-'.join([str(t.tm_year), \ + #str(t.tm_mon), str(t.tm_mday)])) + song_info['year'] = unicode('-'.join([str(t.tm_year), \ + str(t.tm_mon), str(t.tm_mday)])) + song_info['song_name'] = modificate_text(i['name']).strip() + song_info['artist_name'] = modificate_text(i['artist_name']).strip() + song_info['album_pic_url'] = re.sub(r'_\d*\.', '_4.', i['album_logo']) + song_info['cd_serial'] = i['cd_serial'] + if cd_serial_auth: + if not args.undescription: + disc_description = self.get_disc_description(\ + 'http://www.xiami.com/album/%s' % i['album_id'], song_info) + if u''.join(self.disc_description_archives.values()) != u'': + if disc_description: + song_info['album_name'] = modificate_text(i['title']).strip() \ + + ' [Disc-' + song_info['cd_serial'] + '] ' + disc_description + file_name = '[Disc-' + song_info['cd_serial'] + '] ' \ + + disc_description + ' ' + song_info['track'] + '.' \ + + song_info['song_name'] + ' - ' + song_info['artist_name'] + '.mp3' + song_info['file_name'] = file_name + #song_info['cd_serial'] = u'1' + else: + song_info['album_name'] = modificate_text(i['title']).strip() \ + + ' [Disc-' + song_info['cd_serial'] + ']' + file_name = '[Disc-' + song_info['cd_serial'] + '] ' \ + + song_info['track'] + '.' + song_info['song_name'] \ + + ' - ' + song_info['artist_name'] + '.mp3' + song_info['file_name'] = file_name + #song_info['cd_serial'] = u'1' + else: + song_info['album_name'] = modificate_text(i['title']).strip() + file_name = '[Disc-' + song_info['cd_serial'] + '] ' \ + + song_info['track'] + '.' + song_info['song_name'] \ + + ' - ' + song_info['artist_name'] + '.mp3' + song_info['file_name'] = file_name + else: + song_info['album_name'] = modificate_text(i['title']).strip() + file_name = '[Disc-' + song_info['cd_serial'] + '] ' + song_info['track'] \ + + '.' + song_info['song_name'] + ' - ' + song_info['artist_name'] + '.mp3' + song_info['file_name'] = file_name + + else: + song_info['album_name'] = modificate_text(i['title']).strip() + file_name = song_info['track'].zfill(z) + '.' + song_info['song_name'] \ + + ' - ' + song_info['artist_name'] + '.mp3' + song_info['file_name'] = file_name + # song_info['low_mp3'] = i['location'] + return song_info + + def get_song_infos(self, song_id): + j = ss.get(url_song % song_id).json() + album_id = j['song']['album_id'] + j = ss.get(url_album % album_id).json() + t = j['album']['description'] + t = parser.unescape(t) + t = parser.unescape(t) + t = re.sub(r'<.+?(http://.+?)".+?>', r'\1', t) + t = re.sub(r'<.+?>([^\n])', r'\1', t) + t = re.sub(r'<.+?>(\r\n|)', u'\n', t) + album_description = re.sub(r'\s\s+', u'\n', t).strip() + cd_serial_auth = j['album']['songs'][-1]['cd_serial'] > u'1' + z = 0 + if not cd_serial_auth: + z = z_index(j['album']['songs']) + for i in j['album']['songs']: + if i['song_id'] == song_id: + song_info = self.get_song_info(album_description, z, cd_serial_auth, i) + return song_info + + def get_album_infos(self, album_id): + j = ss.get(url_album % album_id).json() + t = j['album']['description'] + t = parser.unescape(t) + t = parser.unescape(t) + t = re.sub(r'<.+?(http://.+?)".+?>', r'\1', t) + t = re.sub(r'<.+?>([^\n])', r'\1', t) + t = re.sub(r'<.+?>(\r\n|)', u'\n', t) + album_description = re.sub(r'\s\s+', u'\n', t).strip() + d = modificate_text(j['album']['title'] + ' - ' + j['album']['artist_name']) + dir_ = os.path.join(os.getcwd().decode('utf8'), d) + self.dir_ = modificate_file_name_for_wget(dir_) + cd_serial_auth = j['album']['songs'][-1]['cd_serial'] > u'1' + z = 0 + if not cd_serial_auth: + z = z_index(j['album']['songs']) + song_infos = [] + for i in j['album']['songs']: + song_info = self.get_song_info(album_description, z, cd_serial_auth, i) + song_infos.append(song_info) + return song_infos + + def download_song(self): + logging.info('url -> http://www.xiami.com/song/%s' % self.song_id) + song_info = self.get_song_infos(self.song_id) + print(s % (97, u'\n >> ' + u'1 首歌曲将要下载.')) \ + if not args.play else '' + self.song_infos = [song_info] + logging.info('directory: %s' % os.getcwd()) + logging.info('total songs: %d' % len(self.song_infos)) + self.download() + + def download_album(self): + logging.info('url -> http://www.xiami.com/album/%s' % self.album_id) + self.song_infos = self.get_album_infos(self.album_id) + amount_songs = unicode(len(self.song_infos)) + print(s % (97, u'\n >> ' + amount_songs + u' 首歌曲将要下载.')) \ + if not args.play else '' + logging.info('directory: %s' % self.dir_) + logging.info('total songs: %d' % len(self.song_infos)) + self.download(amount_songs) + + def download_collect(self): + logging.info('url -> http://www.xiami.com/song/showcollect/id/%s' \ + % self.showcollect_id) + j = ss.get(url_collect % self.showcollect_id).json() + d = modificate_text(j['collect']['name']) + dir_ = os.path.join(os.getcwd().decode('utf8'), d) + self.dir_ = modificate_file_name_for_wget(dir_) + amount_songs = unicode(len(j['collect']['songs'])) + print(s % (97, u'\n >> ' + amount_songs + u' 首歌曲将要下载.')) \ + if not args.play else '' + logging.info('directory: %s' % self.dir_) + logging.info('total songs: %d' % len(j['collect']['songs'])) + n = 1 + for i in j['collect']['songs']: + song_id = i['song_id'] + song_info = self.get_song_infos(song_id) + self.song_infos = [song_info] + self.download(amount_songs, n) + self.html = '' + self.disc_description_archives = {} + n += 1 + + def download_artist_albums(self): + ii = 1 + while True: + j = ss.get(url_artist_albums % (self.artist_id, str(ii))).json() + if j['albums']: + for i in j['albums']: + self.album_id = i['album_id'] + self.download_album() + self.html = '' + self.disc_description_archives = {} + else: + break + ii += 1 + + def download_artist_top_20_songs(self): + logging.info('url (top20) -> http://www.xiami.com/artist/%s' \ + % self.artist_id) + j = ss.get(url_artist_top_song % self.artist_id).json() + d = modificate_text(j['songs'][0]['artist_name'] + u' - top 20') + dir_ = os.path.join(os.getcwd().decode('utf8'), d) + self.dir_ = modificate_file_name_for_wget(dir_) + amount_songs = unicode(len(j['songs'])) + print(s % (97, u'\n >> ' + amount_songs + u' 首歌曲将要下载.')) \ + if not args.play else '' + logging.info('directory: %s' % self.dir_) + logging.info('total songs: %d' % len(j['songs'])) + n = 1 + for i in j['songs']: + song_id = i['song_id'] + song_info = self.get_song_infos(song_id) + self.song_infos = [song_info] + self.download(amount_songs, n) + self.html = '' + self.disc_description_archives = {} + n += 1 + + def download_user_songs(self): + logging.info('url -> http://www.xiami.com/u/%s' % self.user_id) + dir_ = os.path.join(os.getcwd().decode('utf8'), u'虾米用户 \ + %s 收藏的歌曲' % self.user_id) + self.dir_ = modificate_file_name_for_wget(dir_) + logging.info('directory: %s' % self.dir_) + ii = 1 + n = 1 + while True: + j = ss.get(url_lib_songs % (self.user_id, str(ii))).json() + if j['songs']: + for i in j['songs']: + song_id = i['song_id'] + song_info = self.get_song_infos(song_id) + self.song_infos = [song_info] + self.download(n) + self.html = '' + self.disc_description_archives = {} + n += 1 + else: + break + ii += 1 + + def display_infos(self, i): + print '\n ----------------' + print ' >>', s % (94, i['file_name']) + print ' >>', s % (95, i['album_name']) + print ' >>', s % (92, 'http://www.xiami.com/song/%s' % i['song_id']) + if i['durl_is_H']: + print ' >>', s % (98, ' < High rate >') + else: + print ' >>', s % (98, ' < Low rate >') + print '' + + def get_mp3_quality(self, durl): + if 'm3.file.xiami.com' in durl: + return 'H' + else: + return 'L' + + def play(self, nnn=None, nn=None): + for i in self.song_infos: + self.record(i['song_id']) + durl = self.get_durl(i['song_id']) + i['durl_is_H'] = 'm3.file' in durl + self.display_infos(i) + os.system('mpv --really-quiet %s' % durl) + timeout = 1 + ii, _, _ = select.select([sys.stdin], [], [], timeout) + if ii: + sys.exit(0) + else: + pass + + def download(self, amount_songs=u'1', n=None): + dir_ = modificate_file_name_for_wget(self.dir_) + cwd = os.getcwd().decode('utf8') + if dir_ != cwd: + if not os.path.exists(dir_): + os.mkdir(dir_) + ii = 1 + for i in self.song_infos: + num = random.randint(0, 100) % 7 + col = s % (num + 90, i['file_name']) + t = modificate_file_name_for_wget(i['file_name']) + file_name = os.path.join(dir_, t) + if os.path.exists(file_name): ## if file exists, no get_durl + #print 'go' + #sys.exit() + if args.undownload: + self.modified_id3(file_name, i) + ii += 1 + continue + else: + ii += 1 + continue + file_name_for_wget = file_name.replace('`', '\`') + if not args.undownload: + durl = self.get_durl(i['song_id']) + mp3_quality = self.get_mp3_quality(durl) + if n == None: + print(u'\n ++ 正在下载: #%s/%s# %s' \ + % (ii, amount_songs, col)) + logging.info(u' #%s/%s [%s] -> %s' \ + % (ii, amount_songs, mp3_quality, i['file_name'])) + else: + print(u'\n ++ 正在下载: #%s/%s# %s' \ + % (n, amount_songs, col)) + logging.info(u' #%s/%s [%s] -> %s' \ + % (n, amount_songs, mp3_quality, i['file_name'])) + if mp3_quality == 'L': + print s % (91, ' !!! Warning: '), 'gaining LOW quality mp3 link.' + wget = self.template_wgets % (file_name_for_wget, durl) + wget = wget.encode('utf8') + status = os.system(wget) + if status != 0: # other http-errors, such as 302. + wget_exit_status_info = wget_es[status] + logging.info(\ + ' \\\n \\->WARN: status: \ + %d (%s), command: %s' % (status, wget_exit_status_info, wget)) + logging.info(' ########### work is over ###########\n') + print( + '\n\n ----### \x1b[1;91mERROR\x1b[0m ==> \x1b[1;91m%d \ + (%s)\x1b[0m ###--- \n\n' % (status, wget_exit_status_info)) + print s % (91, ' ===> '), wget + sys.exit(1) + else: + os.rename('%s.tmp' % file_name, file_name) + + self.modified_id3(file_name, i) + ii += 1 + time.sleep(0) + +def main(url): + x = xiami(url) + x.init() + x.url_parser() + logging.info(' ########### work is over ###########\n') + +if __name__ == '__main__': + log_file = os.path.join(os.path.expanduser('~'), '.Xiami.log') + logging.basicConfig(filename=log_file, format='%(asctime)s %(message)s') + print(s % (91, u'\n 程序运行日志在 %s' % log_file)) + p = argparse.ArgumentParser(description='downloading any xiami.com') + p.add_argument('url', help='any url of xiami.com') + p.add_argument('-p', '--play', action='store_true', \ + help='play with mpv') + p.add_argument('-d', '--undescription', action='store_true', \ + help='no add disk\'s distribution') + p.add_argument('-c', '--undownload', action='store_true', \ + help='no download, using to renew id3 tags') + args = p.parse_args() + main(args.url)