Skip to content

Instantly share code, notes, and snippets.

@PeterDing
Last active December 29, 2015 13:29
Show Gist options
  • Save PeterDing/7677236 to your computer and use it in GitHub Desktop.
Save PeterDing/7677236 to your computer and use it in GitHub Desktop.
虾米(xiami.com)终端播放器,支持song, album, showcollect, artist(top 20), user。 输入账号后,可以将播放的歌曲记录到 http://www.xiami.com/space/charts-recent/u/{id}。 Usage: xiami_player.py url -----------播放时,一下enter,进入下一曲。两下enter,退出。 -- # 移到仓库 https://github.com/PeterDing/iScript
#!/usr/bin/env python2
# vim: set fileencoding=utf8
import re, StringIO, sys, os, time, json, gzip, urllib2, argparse, select
from HTMLParser import HTMLParser
parser = HTMLParser()
s = u'\x1b[1;%dm%s\x1b[0m' # terminual color template
email = '' # 输入账号后,可以将播放的歌曲记录到 http://www.xiami.com/space/charts-recent/u/{id}
password = '' # vip账号默认启动高品质音乐播放
#############################################################
# 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_top_song = "http://www.xiami.com/app/android/artist-topsongs?id=%s"
url_rndsongs = "http://www.xiami.com/app/android/rnd?uid=%s"
url_lib_songs = "http://www.xiami.com/app/android/lib-songs?uid=%s&page=%s"
# }}}
############################################################
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 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
#############################################################
# from https://gist.github.com/lepture/1014329
#############################################################
# {{{
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2011, lepture.com
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
# * Neither the name of the author nor the names of its contributors
# may be used to endorse or promote products derived from this
# software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import urllib
import httplib
from contextlib import closing
from Cookie import SimpleCookie
ua = 'Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36'
#ua = 'Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19'
checkin_headers = {
'User-Agent': ua,
'Content-Length': '0',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'X-Requested-With': 'XMLHttpRequest',
'Host': 'www.xiami.com',
'Origin': 'http://www.xiami.com/',
'Referer': 'http://www.xiami.com/',
'Content-Length': '0',
}
class xiami_login(object):
def __init__(self, email, password):
self.email = email
self.password = password
self._auth = None
def login(self):
_form = {
'email': self.email,
'password': self.password,
'LoginButton': '登录',
}
data = urllib.urlencode(_form)
headers = {'User-Agent': ua}
headers['Referer'] = 'http://www.xiami.com/web/login'
headers['Content-Type'] = 'application/x-www-form-urlencoded'
with closing(httplib.HTTPConnection('www.xiami.com')) as conn:
conn.request('POST', '/web/login', data, headers)
res = conn.getresponse()
cookie = res.getheader('Set-Cookie')
self._auth = SimpleCookie(cookie)['member_auth'].value
return self._auth
def checkin(self):
if not self._auth:
self.login()
headers = checkin_headers
headers['Cookie'] = 'member_auth=%s; t_sign_auth=1' % self._auth
with closing(httplib.HTTPConnection('www.xiami.com')) as conn:
conn.request('POST', '/task/signin', None, headers)
res = conn.getresponse()
return res.read()
# }}}
########################################################
class xiami(object):
def __init__(self, url):
self.url = url
self.song_infos = []
self.json_url = ''
self.cookie = ''
self.template_song = 'http://www.xiami.com/song/gethqsong/sid/%s'
self.template_record = 'http://www.xiami.com/count/playrecord?sid=%s'
def init(self, email=email, password=password):
home = os.path.expanduser('~')
cookie_dir = os.path.join(home, '.Xm.cookie') # directory of login_time and member_auth cookie of xiami.com
xm = xiami_login(email, password)
if os.path.exists(cookie_dir):
f = open(cookie_dir).read().split('\n')
tm = str(int(time.time()))
tm_s = (int(tm) - int(f[0])) > 60*60*24*7 # 5 hours later, login after next loginning
xm._auth = f[1]
member_auth = xm._auth
auth = xm.checkin()
if auth != '0' or tm_s:
member_auth = xm.login()
tm = str(int(time.time()))
open(cookie_dir, 'w').write(tm + '\n' + member_auth)
self.member_auth = member_auth
else:
self.member_auth = member_auth
else:
member_auth = xm.login()
tm = str(int(time.time()))
open(cookie_dir, 'w').write(tm + '\n' + member_auth)
self.member_auth = member_auth
def get_durl(self, id_):
opener2 = urllib2.build_opener()
opener2.addheaders = [
('Referer', 'http://img.xiami.net/static/swf/seiya/player.swf?v=%s' % int(time.time()*(10**3))), # 'Referer' is indispensable
#('Referer', 'http://img.xiami.net/static/swf/seiya/player.swf'), # 'Referer' is indispensable
('User-Agent', ua),
('Accept-Language', 'en-US,en;q=0.8,zh-CN;q=0.6,zh;q=0.4,zh-TW;q=0.2'),
('Cookie', 'member_auth=%s' % self.member_auth)
]
api_json = opener2.open(self.template_song % id_).read()
j = json.loads(api_json)
t = j['location']
row = t[0]
encryed_url = t[1:]
durl = decry(row, encryed_url)
return durl
def record(self, id_):
try:
api_json = self.opener.open(self.template_record % id_).read()
except:
pass
def url_parser(self):
if '/showcollect/' in self.url:
self.showcollect_id = re.search(r'/showcollect/id/(\d+)', self.url).group(1)
self.json_url = url_collect % self.showcollect_id
self.get_collect_infos()
elif '/album/' in self.url:
self.album_id = re.search(r'/album/(\d+)', self.url).group(1)
self.json_url = url_album % self.album_id
self.get_album_infos()
elif '/artist/' in self.url:
self.artist_id = re.search(r'/artist/(\d+)', self.url).group(1)
self.json_url = url_artist_top_song % self.artist_id
self.get_artist_top_20_songs_infos()
elif '/song/' in self.url:
self.song_id = re.search(r'/song/(\d+)', self.url).group(1)
self.json_url = url_song % self.song_id
self.get_song_infos()
elif '/u/' in self.url:
self.user_id = re.search(r'/u/(\d+)', self.url).group(1)
argv = raw_input( s % (92, ' >> listen collected songs (c) or radio (r): '))
if argv == 'c':
i = raw_input( s % (92, ' page number: '))
if i.isdigit():
i = int(i)
while True:
self.json_url = url_lib_songs % (self.user_id, i)
self.get_user_infos()
i += 1
else:
print s % (91, ' please enter a number')
elif argv == 'r':
self.json_url = url_rndsongs % self.user_id
self.get_user_infos()
else:
print s % (91, ' please enter c or r')
def get_song_infos(self):
api_json = self.opener.open(self.json_url).read()
j = json.loads(api_json)
song_info = {}
song_info['song_id'] = j['song']['song_id']
song_info['index'] = u''
song_info['track'] = u''
song_info['song_name'] = modificate_text(j['song']['song_name']).strip()
song_info['album_name'] = modificate_text(j['song']['album_name']).strip()
song_info['artist_name'] = modificate_text(j['song']['artist_name']).strip()
file_name = song_info['song_name'] + ' - ' + song_info['artist_name'] + '.mp3'
song_info['file_name'] = file_name
#song_info['low_mp3'] = j['song']['song_location']
self.song_infos.append(song_info)
self.do()
def get_album_infos(self):
api_json = self.opener.open(self.json_url).read()
j = json.loads(api_json)
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']:
song_info = {}
song_info['song_id'] = i['song_id']
song_info['track'] = i['track']
song_info['song_name'] = modificate_text(i['name']).strip()
song_info['cd_serial'] = i['cd_serial']
song_info['artist_name'] = modificate_text(i['artist_name']).strip()
if song_info['cd_serial'] and cd_serial_auth:
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['album_name'] = modificate_text(i['title']).strip() + ' [Disc-' + i['cd_serial'] + ']'
else:
file_name = song_info['track'].zfill(z) + '.' + song_info['song_name'] + ' - ' + song_info['artist_name'] + '.mp3'
song_info['file_name'] = file_name
song_info['album_name'] = modificate_text(i['title']).strip()
song_info['low_mp3'] = i['location']
self.song_infos.append(song_info)
self.do()
def get_collect_infos(self):
api_json = self.opener.open(self.json_url).read()
j = json.loads(api_json)
z = z_index(j['collect']['songs'])
ii = 1
for i in j['collect']['songs']:
song_info = {}
song_info['song_id'] = i['song_id']
song_info['index'] = unicode(ii).zfill(z)
song_info['track'] = u''
song_info['song_name'] = modificate_text(i['name']).strip()
song_info['album_name'] = modificate_text(i['title']).strip()
song_info['artist_name'] = modificate_text(i['artist_name']).strip()
file_name = song_info['index'] + '.' + song_info['song_name'] + ' - ' + song_info['artist_name'] + '.mp3'
song_info['file_name'] = file_name
song_info['low_mp3'] = i['location']
self.song_infos.append(song_info)
ii += 1
self.do()
def get_artist_top_20_songs_infos(self):
api_json = self.opener.open(self.json_url).read()
j = json.loads(api_json)
z = z_index(j['songs'])
ii = 1
for i in j['songs']:
song_info = {}
song_info['song_id'] = i['song_id']
song_info['index'] = unicode(ii).zfill(z)
song_info['track'] = u''
song_info['song_name'] = modificate_text(i['name']).strip()
song_info['album_name'] = modificate_text(i['title']).strip()
song_info['artist_name'] = modificate_text(i['artist_name']).strip()
file_name = song_info['index'] + '.' + song_info['song_name'] + ' - ' + song_info['artist_name'] + '.mp3'
song_info['file_name'] = file_name
song_info['low_mp3'] = i['location']
self.song_infos.append(song_info)
ii += 1
self.do()
def get_user_infos(self):
api_json = self.opener.open(self.json_url).read()
j = json.loads(api_json)
z = z_index(j['songs'])
for i in j['songs']:
song_info = {}
song_info['song_id'] = i['song_id']
song_info['song_name'] = modificate_text(i['name']).strip()
song_info['artist_name'] = modificate_text(i['artist_name']).strip()
file_name = song_info['song_name'] + ' - ' + song_info['artist_name'] + '.mp3'
song_info['file_name'] = file_name
song_info['album_name'] = modificate_text(i['title']).strip()
song_info['low_mp3'] = i['location']
self.song_infos.append(song_info)
self.do()
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 do_H(self):
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 do_L(self):
for i in self.song_infos:
self.record(i['song_id'])
durl = i['low_mp3']
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 main(args):
x = xiami(args.url)
if args.high_rate:
x.do = x.do_H
elif args.low_rate:
x.do = x.do_L
else:
x.do = x.do_H
x.init()
opener = urllib2.build_opener()
opener.addheaders = [
('Accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'),
('Accept-Language', 'en-US,en;q=0.8,zh-CN;q=0.6,zh;q=0.4,zh-TW;q=0.2'),
('User-Agent', ua),
('Cookie', 'member_auth=%s' % x.member_auth)
]
x.opener = opener
x.url_parser()
if __name__ == '__main__':
p = argparse.ArgumentParser()
t = p.add_mutually_exclusive_group()
t.add_argument('-t', '--high_rate', action='store_true')
t.add_argument('-l', '--low_rate', action='store_true')
p.add_argument('url', help='xiami url')
args = p.parse_args()
main(args)
@wozzup
Copy link

wozzup commented Apr 26, 2014

url_search_all, url_search_parts API url is dead

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment