├── Bangumi ├── Source │ ├── Feedback.py │ ├── bangumi.py │ ├── bangumis.py │ ├── biclass.py │ ├── icon.png │ ├── support.py │ └── zhuanti.py └── bangumi.alfredworkflow ├── Bilibili Hot ├── Bilibili Hot.alfredworkflow └── Source │ ├── biclass.py │ ├── blhot.py │ ├── icon.png │ └── support.py ├── BilibiliSearch ├── Source │ ├── 30C75941-C36F-4C44-9222-52F3ACFE774C.png │ ├── Feedback.py │ ├── biclass.py │ ├── bilibiliSearch.py │ ├── icon.png │ ├── info.plist │ └── support.py └── bilibili.alfredworkflow ├── Biliurl ├── Source │ ├── Feedback.py │ ├── biclass.py │ ├── icon.png │ ├── main.py │ └── support.py └── biliurl.alfredworkflow ├── GetAssFromBilibili ├── danmu.alfredworkflow └── source │ ├── Feedback.py │ ├── biclass.py │ ├── getAss.py │ ├── getVedio.py │ ├── icon.png │ └── support.py ├── LICENSE ├── README.md ├── img ├── bgm1.png ├── bgm2.png ├── bgm3.png ├── bgm4.png ├── bgm5.gif ├── bgm6.gif ├── bhot1.png ├── bhot2.png ├── bhot3.gif ├── bl1.png ├── bl2.gif ├── url1.png └── xml2ass.png └── xml2ass ├── source ├── Feedback.py ├── GetAssDanmaku.py ├── biclass.py ├── bilibili.py ├── convert.py ├── support.py └── xml2ass.py └── xml2ass.alfredworkflow /Bangumi/Source/Feedback.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Fri Sep 26 00:07:49 2014 4 | 5 | @author: vespa 6 | """ 7 | import sys 8 | 9 | import xml.etree.ElementTree as et 10 | default_encoding = 'utf-8' 11 | if sys.getdefaultencoding() != default_encoding: 12 | reload(sys) 13 | sys.setdefaultencoding(default_encoding) 14 | 15 | class Feedback(): 16 | """Feeback used by Alfred Script Filter 17 | 18 | Usage: 19 | fb = Feedback() 20 | fb.add_item('Hello', 'World') 21 | fb.add_item('Foo', 'Bar') 22 | print fb 23 | 24 | """ 25 | 26 | def __init__(self): 27 | self.feedback = et.Element('items') 28 | 29 | def __repr__(self): 30 | """XML representation used by Alfred 31 | 32 | Returns: 33 | XML string 34 | """ 35 | return et.tostring(self.feedback) 36 | 37 | def add_item(self, title, subtitle = "", arg = "", valid = "yes", autocomplete = "", icon = "icon.png"): 38 | """ 39 | Add item to alfred Feedback 40 | 41 | Args: 42 | title(str): the title displayed by Alfred 43 | Keyword Args: 44 | subtitle(str): the subtitle displayed by Alfred 45 | arg(str): the value returned by alfred when item is selected 46 | valid(str): whether or not the entry can be selected in Alfred to trigger an action 47 | autcomplete(str): the text to be inserted if an invalid item is selected. This is only used if 'valid' is 'no' 48 | icon(str): filename of icon that Alfred will display 49 | """ 50 | item = et.SubElement(self.feedback, 'item', uid=str(len(self.feedback)), arg=arg, valid=valid, autocomplete=autocomplete) 51 | _title = et.SubElement(item, 'title') 52 | _title.text = title 53 | _sub = et.SubElement(item, 'subtitle') 54 | _sub.text = subtitle 55 | _icon = et.SubElement(item, 'icon') 56 | _icon.text = icon -------------------------------------------------------------------------------- /Bangumi/Source/bangumi.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Mon May 26 23:42:03 2014 4 | 5 | @author: Administrator 6 | """ 7 | 8 | from support import * 9 | from Feedback import * 10 | import hashlib 11 | import datetime 12 | 13 | def GetSign(params,appkey,AppSecret=None): 14 | """ 15 | 获取新版API的签名,不然会返回-3错误 16 | 待添加:【重要!】 17 | 需要做URL编码并保证字母都是大写,如 %2F 18 | """ 19 | params['appkey']=appkey; 20 | data = ""; 21 | paras = params.keys(); 22 | paras.sort(); 23 | for para in paras: 24 | if data != "": 25 | data += "&"; 26 | data += para + "=" + params[para]; 27 | if AppSecret == None: 28 | return data 29 | m = hashlib.md5() 30 | m.update(data+AppSecret) 31 | return data+'&sign='+m.hexdigest() 32 | 33 | def GetGangumi(appkey,btype,weekday,mode,week): 34 | """ 35 | 获取新番信息 36 | 输入: 37 | btype:番剧类型 2: 二次元新番 3: 三次元新番 默认:所有 38 | weekday:周一:1 周二:2 ...周六:6 39 | """ 40 | paras = {}; 41 | paras['btype'] = GetString(btype) 42 | if weekday != None: 43 | paras['weekday'] = GetString(weekday) 44 | url = 'http://api.bilibili.cn/bangumi?' + GetSign(paras,appkey,None); 45 | jsoninfo = JsonInfo(url); 46 | bangumilist = []; 47 | for bgm in jsoninfo.Getvalue('list'): 48 | if mode == 't' and bgm['weekday'] != int(time.strftime("%w",time.localtime())): 49 | continue; 50 | if mode != 't' and week != None and bgm['weekday'] != week: 51 | continue 52 | bangumi = Bangumi(); 53 | bangumi.lastupdate = bgm['lastupdate'] 54 | bangumi.title = bgm['title'] 55 | bangumi.lastupdate_at = bgm['lastupdate_at'] 56 | bangumi.weekday = bgm['weekday'] 57 | bangumi.bgmcount = bgm['bgmcount'] 58 | bangumilist.append(bangumi) 59 | if mode == 'r': 60 | bangumilist = sorted(bangumilist,key=lambda x:x.lastupdate) 61 | bangumilist.reverse(); 62 | if len(bangumilist) > 20: 63 | bangumilist = bangumilist[0:20] 64 | return bangumilist 65 | 66 | def datetime_timestamp(dt): 67 | time.strptime(dt, '%Y-%m-%d %H:%M:%S') 68 | s = time.mktime(time.strptime(dt, '%Y-%m-%d %H:%M:%S')) 69 | return int(s) 70 | 71 | def timestamp_datetime(value): 72 | format = '%Y-%m-%d %H:%M:%S' 73 | value = time.localtime(value) 74 | dt = time.strftime(format, value) 75 | return dt 76 | 77 | def Getweek(num): 78 | string = ['日','一','二','三','四','五','六'] 79 | return string[num] 80 | 81 | 82 | fb = Feedback() 83 | query = '{query}' 84 | # t:今天新番 85 | # r:最近更新 86 | # 3:三次元 87 | # wn:查询星期n 88 | 89 | appkey='03fc8eb101b091fb'; 90 | 91 | qtype = 2; 92 | qmode = 'r' 93 | qweek = None 94 | opt = re.findall(r'^(\d)',query) 95 | if opt != []: 96 | qtype = int(opt[0]) 97 | if qtype not in [2,3]: 98 | qtype = 2 99 | 100 | opt = re.findall(r'w(\d)',query) 101 | if opt != []: 102 | qweek = int(opt[0]) 103 | if not 0 <= qweek <= 6: 104 | qweek = None 105 | 106 | 107 | opt = re.findall(r'r',query) 108 | if opt != []: 109 | qmode = 'r' 110 | 111 | opt = re.findall(r't',query) 112 | if opt != []: 113 | qmode = 't' 114 | 115 | 116 | bangumilist = GetGangumi(appkey,btype = qtype,weekday=0,mode = qmode,week = qweek); 117 | try: 118 | for bgm in bangumilist: 119 | fb.add_item(bgm.title,subtitle="【周%s】最后更新时间:%s,现有%s集"%(Getweek(bgm.weekday),bgm.lastupdate_at,bgm.bgmcount),arg=bgm.title) 120 | 121 | except SyntaxError as e: 122 | if ('EOF', 'EOL' in e.msg): 123 | fb.add_item('...') 124 | else: 125 | fb.add_item('SyntaxError', e.msg) 126 | except Exception as e: 127 | fb.add_item(e.__class__.__name__,subtitle=e.message) 128 | print fb -------------------------------------------------------------------------------- /Bangumi/Source/bangumis.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Mon May 26 23:42:03 2014 4 | 5 | @author: Administrator 6 | """ 7 | 8 | from support import * 9 | from Feedback import * 10 | import hashlib 11 | import datetime 12 | 13 | def GetSign(params,appkey,AppSecret=None): 14 | """ 15 | 获取新版API的签名,不然会返回-3错误 16 | 待添加:【重要!】 17 | 需要做URL编码并保证字母都是大写,如 %2F 18 | """ 19 | params['appkey']=appkey; 20 | data = ""; 21 | paras = params.keys(); 22 | paras.sort(); 23 | for para in paras: 24 | if data != "": 25 | data += "&"; 26 | data += para + "=" + params[para]; 27 | if AppSecret == None: 28 | return data 29 | m = hashlib.md5() 30 | m.update(data+AppSecret) 31 | return data+'&sign='+m.hexdigest() 32 | 33 | def GetGangumi(appkey,btype,weekday,mode,week): 34 | """ 35 | 获取新番信息 36 | 输入: 37 | btype:番剧类型 2: 二次元新番 3: 三次元新番 默认:所有 38 | weekday:周一:1 周二:2 ...周六:6 39 | """ 40 | paras = {}; 41 | paras['btype'] = GetString(btype) 42 | if weekday != None: 43 | paras['weekday'] = GetString(weekday) 44 | url = 'http://api.bilibili.cn/bangumi?' + GetSign(paras,appkey,None); 45 | jsoninfo = JsonInfo(url); 46 | bangumilist = []; 47 | for bgm in jsoninfo.Getvalue('list'): 48 | if mode == 't' and bgm['weekday'] != int(time.strftime("%w",time.localtime())): 49 | continue; 50 | if mode != 't' and week != None and bgm['weekday'] != week: 51 | continue 52 | bangumi = Bangumi(); 53 | bangumi.lastupdate = bgm['lastupdate'] 54 | bangumi.title = bgm['title'] 55 | bangumi.lastupdate_at = bgm['lastupdate_at'] 56 | bangumi.weekday = bgm['weekday'] 57 | bangumi.bgmcount = bgm['bgmcount'] 58 | bangumi.spid = bgm['spid'] 59 | bangumi.season_id = bgm['season_id'] 60 | bangumilist.append(bangumi) 61 | if mode == 'r': 62 | bangumilist = sorted(bangumilist,key=lambda x:x.lastupdate) 63 | bangumilist.reverse(); 64 | if len(bangumilist) > 30: 65 | bangumilist = bangumilist[0:30] 66 | return bangumilist 67 | 68 | def datetime_timestamp(dt): 69 | time.strptime(dt, '%Y-%m-%d %H:%M:%S') 70 | s = time.mktime(time.strptime(dt, '%Y-%m-%d %H:%M:%S')) 71 | return int(s) 72 | 73 | def timestamp_datetime(value): 74 | format = '%Y-%m-%d %H:%M:%S' 75 | value = time.localtime(value) 76 | dt = time.strftime(format, value) 77 | return dt 78 | 79 | def Getweek(num): 80 | string = ['日','一','二','三','四','五','六'] 81 | return string[num] 82 | 83 | 84 | fb = Feedback() 85 | query = '{query}' 86 | # t:今天新番 87 | # r:最近更新 88 | # 3:三次元 89 | # wn:查询星期n 90 | 91 | appkey='03fc8eb101b091fb'; 92 | 93 | qtype = 2; 94 | qmode = 'r' 95 | qweek = None 96 | opt = re.findall(r'^(\d)',query) 97 | if opt != []: 98 | qtype = int(opt[0]) 99 | if qtype not in [2,3]: 100 | qtype = 2 101 | 102 | opt = re.findall(r'w(\d)',query) 103 | if opt != []: 104 | qweek = int(opt[0]) 105 | if not 0 <= qweek <= 6: 106 | qweek = None 107 | 108 | 109 | opt = re.findall(r'r',query) 110 | if opt != []: 111 | qmode = 'r' 112 | 113 | opt = re.findall(r't',query) 114 | if opt != []: 115 | qmode = 't' 116 | 117 | 118 | bangumilist = GetGangumi(appkey,btype = qtype,weekday=0,mode = qmode,week = qweek); 119 | try: 120 | for bgm in bangumilist: 121 | fb.add_item(bgm.title,subtitle="【周%s】最后更新时间:%s,现有%s集"%(Getweek(bgm.weekday),bgm.lastupdate_at,bgm.bgmcount),arg=str(bgm.spid)+"---"+str(bgm.season_id)) 122 | 123 | except SyntaxError as e: 124 | if ('EOF', 'EOL' in e.msg): 125 | fb.add_item('...') 126 | else: 127 | fb.add_item('SyntaxError', e.msg) 128 | except Exception as e: 129 | fb.add_item(e.__class__.__name__,subtitle=e.message) 130 | print fb -------------------------------------------------------------------------------- /Bangumi/Source/biclass.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Wed May 28 01:22:20 2014 4 | 5 | @author: Administrator 6 | """ 7 | 8 | class User(): 9 | def __init__(self,m_mid=None,m_name=None): 10 | if m_mid: 11 | self.mid = m_mid; 12 | if m_name: 13 | self.name = m_name; 14 | def saveToFile(self,fid): 15 | fid.write('名字:%s\n'%self.name); 16 | fid.write('id:%s\n'%self.mid); 17 | fid.write('是否认证:%s\n'%self.isApprove); 18 | fid.write('空间:%s\n'%self.spaceName); 19 | fid.write('性别:%s\n'%self.sex); 20 | fid.write('账号显示标识:%s\n'%self.rank); 21 | fid.write('头像:%s\n'%self.avatar); 22 | fid.write('关注好友数目:%d\n'%self.follow); 23 | fid.write('粉丝数目:%d\n'%self.fans); 24 | fid.write('投稿数:%d\n'%self.article); 25 | fid.write('地点:%s\n'%self.place); 26 | fid.write('认证信息:%s\n'%self.description); 27 | fid.write('关注好友:\n'); 28 | if self.followlist: 29 | for fo in self.followlist: 30 | fid.write('\t%s\n'%fo); 31 | # 获取空间地址 32 | def GetSpace(self): 33 | return 'http://space.bilibili.tv/'+str(self.mid); 34 | mid = None; 35 | name = None; 36 | isApprove = False;#是否是认证账号 37 | spaceName = ""; 38 | sex = "" 39 | rank = None; 40 | avatar = None; 41 | follow = 0;#关注好友数目 42 | fans = 0;#粉丝数目 43 | article = 0;#投稿数 44 | place = None;#所在地 45 | description = None;#认证用户为认证信息 普通用户为交友宣言 46 | followlist = None;#关注的好友列表 47 | 48 | 49 | class Vedio(): 50 | def __init__(self,m_aid=None,m_title=None): 51 | if m_aid: 52 | self.aid = m_aid; 53 | if m_title: 54 | self.title = m_title; 55 | # 写到文件中 56 | def saveToFile(self,fid): 57 | fid.write('av号:%d\n'%self.aid); 58 | fid.write('标题:%s\n'%self.title); 59 | fid.write('观看:%d\n'%self.guankan); 60 | fid.write('收藏:%d\n'%self.shoucang); 61 | fid.write('弹幕:%d\n'%self.danmu); 62 | fid.write('日期:%s\n'%self.date); 63 | fid.write('封面地址:%s\n'%self.cover); 64 | fid.write('Up主:\n'); 65 | self.author.saveToFile(fid); 66 | fid.write('\n'); 67 | aid = None; 68 | title = None; 69 | guankan = None; 70 | shoucang = None; 71 | danmu = None; 72 | date = None; 73 | cover = None; 74 | commentNumber = None; 75 | description = None; 76 | tag = None; 77 | author = None; 78 | page = None; 79 | credit = None; 80 | coin = None; 81 | spid = None; 82 | cid = None; 83 | offsite = None;#Flash播放调用地址 84 | Iscopy = None; 85 | subtitle = None; 86 | duration = None; 87 | #不明: 88 | tid = None; 89 | typename = None; 90 | instant_server = None; 91 | src = None; 92 | partname = None; 93 | #播放信息: 94 | play_site = None; 95 | play_forward = None; 96 | play_mobile = None; 97 | 98 | class Bangumi(): 99 | def __init__(self): 100 | pass; 101 | typeid = None; 102 | lastupdate = None; 103 | areaid = None; 104 | bgmcount = None;#番剧当前总集数 105 | title = None; 106 | lastupdate_at = None; 107 | attention = None; 108 | cover = None; 109 | priority = None; 110 | area = None; 111 | weekday = None; 112 | spid = None; 113 | new = None; 114 | scover = None; 115 | mcover = None; 116 | click = None; 117 | recentupdate = None; 118 | 119 | class Comment(): 120 | def __init__(self): 121 | self.post_user = User(); 122 | lv = None;#楼层 123 | fbid = None;#评论id 124 | msg = None; 125 | ad_check = None;#状态 (0: 正常 1: UP主隐藏 2: 管理员删除 3: 因举报删除) 126 | post_user = None; 127 | 128 | class CommentList(): 129 | def __init__(self): 130 | pass; 131 | comments = None; 132 | commentLen = None; 133 | page = None; 134 | 135 | -------------------------------------------------------------------------------- /Bangumi/Source/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vespa314/BilibiliAlfredWorkFlows/43f3a27d35bd28118bf9935d4236344b2d5d86d7/Bangumi/Source/icon.png -------------------------------------------------------------------------------- /Bangumi/Source/support.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Mon May 26 23:59:09 2014 4 | 5 | @author: Administrator 6 | """ 7 | import urllib2 8 | import re 9 | import json 10 | from biclass import * 11 | import time 12 | def GetRE(content,regexp): 13 | return re.findall(regexp, content) 14 | 15 | def getURLContent(url): 16 | while True: 17 | flag = 1; 18 | try: 19 | headers = {'User-Agent':'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6'} 20 | req = urllib2.Request(url = url,headers = headers); 21 | content = urllib2.urlopen(req).read(); 22 | except: 23 | flag = 0; 24 | time.sleep(5) 25 | if flag == 1: 26 | break; 27 | return content; 28 | 29 | #def FromJson(url): 30 | # return json.loads(getURLContent(url)) 31 | 32 | class JsonInfo(): 33 | def __init__(self,url): 34 | self.info = json.loads(getURLContent(url)); 35 | def Getvalue(self,*keys): 36 | if len(keys) == 0: 37 | return None 38 | if self.info.has_key(keys[0]): 39 | temp = self.info[keys[0]]; 40 | else: 41 | return None; 42 | if len(keys) > 1: 43 | for key in keys[1:]: 44 | if temp.has_key(key): 45 | temp = temp[key] 46 | else: 47 | return None; 48 | return temp 49 | info = None; 50 | 51 | def GetString(t): 52 | if type(t) == int: 53 | return str(t) 54 | return t 55 | 56 | def getint(string): 57 | try: 58 | i = int(string) 59 | except: 60 | i = 0 61 | return i 62 | 63 | #从视频源码获取视频信息 64 | def GetVedioFromRate(content): 65 | #av号和标题 66 | regular1 = r'([^/]+)'; 67 | info1 = GetRE(content,regular1) 68 | #观看数 69 | regular2 = r'(.+)'; 70 | info2 = GetRE(content,regular2) 71 | #收藏 72 | regular3 = r'(.+)'; 73 | info3 = GetRE(content,regular3) 74 | #弹幕 75 | regular4 = r'(.+)'; 76 | info4 = GetRE(content,regular4) 77 | #日期 78 | regular5 = r'(\d+-\d+-\d+ \d+:\d+)'; 79 | info5 = GetRE(content,regular5) 80 | #封面 81 | regular6 = r''; 82 | info6 = GetRE(content,regular6) 83 | #Up的id和名字 84 | regular7 = r'(.+)' 85 | info7 = GetRE(content,regular7) 86 | #!!!!!!!!这里可以断言所有信息长度相等 87 | vedioNum = len(info1);#视频长度 88 | vedioList = []; 89 | for i in range(vedioNum): 90 | vedio_t = Vedio(); 91 | vedio_t.aid = getint(info1[i][0]); 92 | vedio_t.title = info1[i][1]; 93 | vedio_t.guankan = getint(info2[i]); 94 | vedio_t.shoucang = getint(info3[i]); 95 | vedio_t.danmu = getint(info4[i]); 96 | vedio_t.date = info5[i]; 97 | vedio_t.cover = info6[i]; 98 | vedio_t.author = User(info7[i][0],info7[i][1]) 99 | vedioList.append(vedio_t); 100 | return vedioList 101 | -------------------------------------------------------------------------------- /Bangumi/Source/zhuanti.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Mon May 26 23:42:03 2014 4 | 5 | @author: Administrator 6 | """ 7 | 8 | from support import * 9 | import hashlib 10 | import datetime 11 | 12 | def GetVedioOfZhuanti(spid,season_id=None,bangumi=None): 13 | """ 14 | 输入: 15 | spid:专题id 16 | season_id:分季ID 17 | bangumi:设置为1返回剧番,不设置或者设置为0返回相关视频 18 | 返回: 19 | 视频列表,包含av号,标题,封面和观看数 20 | """ 21 | url = ' http://api.bilibili.cn/spview?spid='+GetString(spid); 22 | if season_id != None: 23 | url += '&season_id='+GetString(season_id); 24 | if bangumi != None: 25 | url += '&bangumi='+GetString(bangumi); 26 | jsoninfo = json.loads(getURLContent(url)) 27 | try: 28 | print jsoninfo['list'][0]['aid'], 29 | except: 30 | pass 31 | 32 | 33 | query = '{query}' 34 | # t:今天新番 35 | # r:最近更新 36 | # 3:三次元 37 | # wn:查询星期n 38 | temp = query.split("---") 39 | GetVedioOfZhuanti(temp[0],bangumi = 1,season_id=temp[1]) 40 | -------------------------------------------------------------------------------- /Bangumi/bangumi.alfredworkflow: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vespa314/BilibiliAlfredWorkFlows/43f3a27d35bd28118bf9935d4236344b2d5d86d7/Bangumi/bangumi.alfredworkflow -------------------------------------------------------------------------------- /Bilibili Hot/Bilibili Hot.alfredworkflow: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vespa314/BilibiliAlfredWorkFlows/43f3a27d35bd28118bf9935d4236344b2d5d86d7/Bilibili Hot/Bilibili Hot.alfredworkflow -------------------------------------------------------------------------------- /Bilibili Hot/Source/biclass.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Wed May 28 01:22:20 2014 4 | 5 | @author: Administrator 6 | """ 7 | 8 | class User(): 9 | def __init__(self,m_mid=None,m_name=None): 10 | if m_mid: 11 | self.mid = m_mid; 12 | if m_name: 13 | self.name = m_name; 14 | def saveToFile(self,fid): 15 | fid.write('名字:%s\n'%self.name); 16 | fid.write('id:%s\n'%self.mid); 17 | fid.write('是否认证:%s\n'%self.isApprove); 18 | fid.write('空间:%s\n'%self.spaceName); 19 | fid.write('性别:%s\n'%self.sex); 20 | fid.write('账号显示标识:%s\n'%self.rank); 21 | fid.write('头像:%s\n'%self.avatar); 22 | fid.write('关注好友数目:%d\n'%self.follow); 23 | fid.write('粉丝数目:%d\n'%self.fans); 24 | fid.write('投稿数:%d\n'%self.article); 25 | fid.write('地点:%s\n'%self.place); 26 | fid.write('认证信息:%s\n'%self.description); 27 | fid.write('关注好友:\n'); 28 | if self.followlist: 29 | for fo in self.followlist: 30 | fid.write('\t%s\n'%fo); 31 | # 获取空间地址 32 | def GetSpace(self): 33 | return 'http://space.bilibili.tv/'+str(self.mid); 34 | mid = None; 35 | name = None; 36 | isApprove = False;#是否是认证账号 37 | spaceName = ""; 38 | sex = "" 39 | rank = None; 40 | avatar = None; 41 | follow = 0;#关注好友数目 42 | fans = 0;#粉丝数目 43 | article = 0;#投稿数 44 | place = None;#所在地 45 | description = None;#认证用户为认证信息 普通用户为交友宣言 46 | followlist = None;#关注的好友列表 47 | 48 | 49 | class Vedio(): 50 | def __init__(self,m_aid=None,m_title=None): 51 | if m_aid: 52 | self.aid = m_aid; 53 | if m_title: 54 | self.title = m_title; 55 | # 写到文件中 56 | def saveToFile(self,fid): 57 | fid.write('av号:%d\n'%self.aid); 58 | fid.write('标题:%s\n'%self.title); 59 | fid.write('观看:%d\n'%self.guankan); 60 | fid.write('收藏:%d\n'%self.shoucang); 61 | fid.write('弹幕:%d\n'%self.danmu); 62 | fid.write('日期:%s\n'%self.date); 63 | fid.write('封面地址:%s\n'%self.cover); 64 | fid.write('Up主:\n'); 65 | self.author.saveToFile(fid); 66 | fid.write('\n'); 67 | aid = None; 68 | title = None; 69 | guankan = None; 70 | shoucang = None; 71 | danmu = None; 72 | date = None; 73 | cover = None; 74 | commentNumber = None; 75 | description = None; 76 | tag = None; 77 | author = None; 78 | page = None; 79 | credit = None; 80 | coin = None; 81 | spid = None; 82 | cid = None; 83 | offsite = None;#Flash播放调用地址 84 | Iscopy = None; 85 | subtitle = None; 86 | duration = None; 87 | #不明: 88 | tid = None; 89 | typename = None; 90 | instant_server = None; 91 | src = None; 92 | partname = None; 93 | #播放信息: 94 | play_site = None; 95 | play_forward = None; 96 | play_mobile = None; 97 | 98 | class Bangumi(): 99 | def __init__(self): 100 | pass; 101 | typeid = None; 102 | lastupdate = None; 103 | areaid = None; 104 | bgmcount = None;#番剧当前总集数 105 | title = None; 106 | lastupdate_at = None; 107 | attention = None; 108 | cover = None; 109 | priority = None; 110 | area = None; 111 | weekday = None; 112 | spid = None; 113 | new = None; 114 | scover = None; 115 | mcover = None; 116 | click = None; 117 | recentupdate = None; 118 | 119 | class Comment(): 120 | def __init__(self): 121 | self.post_user = User(); 122 | lv = None;#楼层 123 | fbid = None;#评论id 124 | msg = None; 125 | ad_check = None;#状态 (0: 正常 1: UP主隐藏 2: 管理员删除 3: 因举报删除) 126 | post_user = None; 127 | 128 | class CommentList(): 129 | def __init__(self): 130 | pass; 131 | comments = None; 132 | commentLen = None; 133 | page = None; 134 | 135 | -------------------------------------------------------------------------------- /Bilibili Hot/Source/blhot.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Mon May 26 23:42:03 2014 4 | 5 | @author: Vespa 6 | """ 7 | 8 | from support import * 9 | import hashlib 10 | import datetime 11 | import sys 12 | import datetime 13 | 14 | import xml.etree.ElementTree as et 15 | default_encoding = 'utf-8' 16 | if sys.getdefaultencoding() != default_encoding: 17 | reload(sys) 18 | sys.setdefaultencoding(default_encoding) 19 | 20 | class Feedback(): 21 | """Feeback used by Alfred Script Filter 22 | 23 | Usage: 24 | fb = Feedback() 25 | fb.add_item('Hello', 'World') 26 | fb.add_item('Foo', 'Bar') 27 | print fb 28 | 29 | """ 30 | 31 | def __init__(self): 32 | self.feedback = et.Element('items') 33 | 34 | def __repr__(self): 35 | """XML representation used by Alfred 36 | 37 | Returns: 38 | XML string 39 | """ 40 | return et.tostring(self.feedback) 41 | 42 | def add_item(self, title, subtitle = "", arg = "", valid = "yes", autocomplete = "", icon = "icon.png"): 43 | """ 44 | Add item to alfred Feedback 45 | 46 | Args: 47 | title(str): the title displayed by Alfred 48 | Keyword Args: 49 | subtitle(str): the subtitle displayed by Alfred 50 | arg(str): the value returned by alfred when item is selected 51 | valid(str): whether or not the entry can be selected in Alfred to trigger an action 52 | autcomplete(str): the text to be inserted if an invalid item is selected. This is only used if 'valid' is 'no' 53 | icon(str): filename of icon that Alfred will display 54 | """ 55 | item = et.SubElement(self.feedback, 'item', uid=str(len(self.feedback)), arg=arg, valid=valid, autocomplete=autocomplete) 56 | _title = et.SubElement(item, 'title') 57 | _title.text = title 58 | _sub = et.SubElement(item, 'subtitle') 59 | _sub.text = subtitle 60 | _icon = et.SubElement(item, 'icon') 61 | _icon.text = icon 62 | 63 | 64 | 65 | def GetSign(params,appkey,AppSecret=None): 66 | """ 67 | 获取新版API的签名,不然会返回-3错误 68 | 待添加:【重要!】 69 | 需要做URL编码并保证字母都是大写,如 %2F 70 | """ 71 | params['appkey']=appkey; 72 | data = ""; 73 | paras = params.keys(); 74 | paras.sort(); 75 | for para in paras: 76 | if data != "": 77 | data += "&"; 78 | data += para + "=" + params[para]; 79 | if AppSecret == None: 80 | return data 81 | m = hashlib.md5() 82 | m.update(data+AppSecret) 83 | return data+'&sign='+m.hexdigest() 84 | 85 | def GetRank(appkey,tid,begin=None,end=None,page = None,pagesize=None,click_detail =None,order = None,AppSecret=None): 86 | paras = {}; 87 | paras['appkey']=appkey; 88 | paras['tid']=GetString(tid); 89 | if order: 90 | paras['order']=order; 91 | if click_detail: 92 | paras['click_detail']=click_detail; 93 | if pagesize: 94 | paras['pagesize']=GetString(pagesize); 95 | if begin != None and len(begin)==3: 96 | paras['begin']='%d-%d-%d'%(begin[0],begin[1],begin[2]); 97 | if end != None and len(end)==3: 98 | paras['end']='%d-%d-%d'%(end[0],end[1],end[2]); 99 | if page: 100 | paras['page']=GetString(page); 101 | if click_detail: 102 | paras['click_detail'] = click_detail; 103 | url = 'http://api.bilibili.cn/list?' + GetSign(paras,appkey,AppSecret); 104 | jsoninfo = JsonInfo(url); 105 | vediolist = []; 106 | for i in range(len(jsoninfo.Getvalue('list'))-1): 107 | idx = str(i); 108 | item = jsoninfo.Getvalue('list',idx) 109 | vedio = Vedio(item['aid'],item['title']) 110 | vedio.guankan = item['play'] 111 | vedio.tid = item['typeid'] 112 | vedio.date = jsoninfo.Getvalue('list',idx,'create') 113 | vedio.author = User(item['mid'],item['author']) 114 | vedio.description = item['description'] 115 | vedio.duration = item['duration'] 116 | vediolist.append(vedio) 117 | return vediolist 118 | 119 | query = '{query}' 120 | fb = Feedback() 121 | appkey = "03fc8eb101b091fb" 122 | dayspan = 3 123 | 124 | modelist = {'dm':'damku','sc':'stow','pl':"review",'yb':'promote'} 125 | mode = None 126 | for k in modelist: 127 | opt = re.findall(k,query) 128 | if opt != []: 129 | mode = modelist[k] 130 | break 131 | if mode == None: 132 | mode = 'hot' 133 | 134 | zonelist = {'dh':1,'yy':3,'yx':4,'kj':36,'yl':5} 135 | zone = None 136 | for k in zonelist: 137 | opt = re.findall(k,query) 138 | if opt != []: 139 | zone = zonelist[k] 140 | break 141 | if zone == None: 142 | zone = 0 143 | 144 | opt = re.findall(r'd(\d+)',query) 145 | if opt != []: 146 | dayspan = int(opt[0]) 147 | if dayspan > 90: 148 | dayspan = 3 149 | 150 | 151 | endday = datetime.datetime.now() 152 | beginday = endday - datetime.timedelta(days =dayspan) 153 | 154 | vediolist = GetRank(appkey,zone,begin=[beginday.year,beginday.month,beginday.day],end=[endday.year,endday.month,endday.day],page = None,pagesize=50,click_detail =None,order = mode,AppSecret=None) 155 | 156 | try: 157 | for bgm in vediolist: 158 | if bgm.tid not in [33,32,94]: 159 | fb.add_item("%s(%s)"%(bgm.title,(bgm.date.split(" "))[0]),subtitle="【%s】%s"%(bgm.author.name,bgm.description),arg=bgm.aid) 160 | 161 | except SyntaxError as e: 162 | if ('EOF', 'EOL' in e.msg): 163 | fb.add_item('...') 164 | else: 165 | fb.add_item('SyntaxError', e.msg) 166 | except Exception as e: 167 | fb.add_item(e.__class__.__name__,subtitle=e.message) 168 | print fb 169 | -------------------------------------------------------------------------------- /Bilibili Hot/Source/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vespa314/BilibiliAlfredWorkFlows/43f3a27d35bd28118bf9935d4236344b2d5d86d7/Bilibili Hot/Source/icon.png -------------------------------------------------------------------------------- /Bilibili Hot/Source/support.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Mon May 26 23:59:09 2014 4 | 5 | @author: Administrator 6 | """ 7 | import urllib2 8 | import re 9 | import json 10 | from biclass import * 11 | import time 12 | def GetRE(content,regexp): 13 | return re.findall(regexp, content) 14 | 15 | def getURLContent(url): 16 | while True: 17 | flag = 1; 18 | try: 19 | headers = {'User-Agent':'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6'} 20 | req = urllib2.Request(url = url,headers = headers); 21 | content = urllib2.urlopen(req).read(); 22 | except: 23 | flag = 0; 24 | time.sleep(5) 25 | if flag == 1: 26 | break; 27 | return content; 28 | 29 | #def FromJson(url): 30 | # return json.loads(getURLContent(url)) 31 | 32 | class JsonInfo(): 33 | def __init__(self,url): 34 | self.info = json.loads(getURLContent(url)); 35 | def Getvalue(self,*keys): 36 | if len(keys) == 0: 37 | return None 38 | if self.info.has_key(keys[0]): 39 | temp = self.info[keys[0]]; 40 | else: 41 | return None; 42 | if len(keys) > 1: 43 | for key in keys[1:]: 44 | if temp.has_key(key): 45 | temp = temp[key] 46 | else: 47 | return None; 48 | return temp 49 | info = None; 50 | 51 | def GetString(t): 52 | if type(t) == int: 53 | return str(t) 54 | return t 55 | 56 | def getint(string): 57 | try: 58 | i = int(string) 59 | except: 60 | i = 0 61 | return i 62 | 63 | #从视频源码获取视频信息 64 | def GetVedioFromRate(content): 65 | #av号和标题 66 | regular1 = r'([^/]+)'; 67 | info1 = GetRE(content,regular1) 68 | #观看数 69 | regular2 = r'(.+)'; 70 | info2 = GetRE(content,regular2) 71 | #收藏 72 | regular3 = r'(.+)'; 73 | info3 = GetRE(content,regular3) 74 | #弹幕 75 | regular4 = r'(.+)'; 76 | info4 = GetRE(content,regular4) 77 | #日期 78 | regular5 = r'(\d+-\d+-\d+ \d+:\d+)'; 79 | info5 = GetRE(content,regular5) 80 | #封面 81 | regular6 = r''; 82 | info6 = GetRE(content,regular6) 83 | #Up的id和名字 84 | regular7 = r'(.+)' 85 | info7 = GetRE(content,regular7) 86 | #!!!!!!!!这里可以断言所有信息长度相等 87 | vedioNum = len(info1);#视频长度 88 | vedioList = []; 89 | for i in range(vedioNum): 90 | vedio_t = Vedio(); 91 | vedio_t.aid = getint(info1[i][0]); 92 | vedio_t.title = info1[i][1]; 93 | vedio_t.guankan = getint(info2[i]); 94 | vedio_t.shoucang = getint(info3[i]); 95 | vedio_t.danmu = getint(info4[i]); 96 | vedio_t.date = info5[i]; 97 | vedio_t.cover = info6[i]; 98 | vedio_t.author = User(info7[i][0],info7[i][1]) 99 | vedioList.append(vedio_t); 100 | return vedioList 101 | -------------------------------------------------------------------------------- /BilibiliSearch/Source/30C75941-C36F-4C44-9222-52F3ACFE774C.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vespa314/BilibiliAlfredWorkFlows/43f3a27d35bd28118bf9935d4236344b2d5d86d7/BilibiliSearch/Source/30C75941-C36F-4C44-9222-52F3ACFE774C.png -------------------------------------------------------------------------------- /BilibiliSearch/Source/Feedback.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Fri Sep 26 00:07:49 2014 4 | 5 | @author: vespa 6 | """ 7 | import sys 8 | 9 | import xml.etree.ElementTree as et 10 | default_encoding = 'utf-8' 11 | if sys.getdefaultencoding() != default_encoding: 12 | reload(sys) 13 | sys.setdefaultencoding(default_encoding) 14 | 15 | class Feedback(): 16 | """Feeback used by Alfred Script Filter 17 | 18 | Usage: 19 | fb = Feedback() 20 | fb.add_item('Hello', 'World') 21 | fb.add_item('Foo', 'Bar') 22 | print fb 23 | 24 | """ 25 | 26 | def __init__(self): 27 | self.feedback = et.Element('items') 28 | 29 | def __repr__(self): 30 | """XML representation used by Alfred 31 | 32 | Returns: 33 | XML string 34 | """ 35 | return et.tostring(self.feedback) 36 | 37 | def add_item(self, title, subtitle = "", arg = "", valid = "yes", autocomplete = "", icon = "icon.png"): 38 | """ 39 | Add item to alfred Feedback 40 | 41 | Args: 42 | title(str): the title displayed by Alfred 43 | Keyword Args: 44 | subtitle(str): the subtitle displayed by Alfred 45 | arg(str): the value returned by alfred when item is selected 46 | valid(str): whether or not the entry can be selected in Alfred to trigger an action 47 | autcomplete(str): the text to be inserted if an invalid item is selected. This is only used if 'valid' is 'no' 48 | icon(str): filename of icon that Alfred will display 49 | """ 50 | item = et.SubElement(self.feedback, 'item', uid=str(len(self.feedback)), arg=arg, valid=valid, autocomplete=autocomplete) 51 | _title = et.SubElement(item, 'title') 52 | _title.text = title 53 | _sub = et.SubElement(item, 'subtitle') 54 | _sub.text = subtitle 55 | _icon = et.SubElement(item, 'icon') 56 | _icon.text = icon -------------------------------------------------------------------------------- /BilibiliSearch/Source/biclass.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Wed May 28 01:22:20 2014 4 | 5 | @author: Vespa 6 | """ 7 | 8 | class User(): 9 | def __init__(self,m_mid=None,m_name=None): 10 | if m_mid: 11 | self.mid = m_mid 12 | if m_name: 13 | if isinstance(m_name,unicode): 14 | m_name = m_name.encode('utf8') 15 | self.name = m_name 16 | # 获取空间地址 17 | def GetSpace(self): 18 | return 'http://space.bilibili.tv/'+str(self.mid) 19 | mid = None 20 | name = None 21 | isApprove = None#是否是认证账号 22 | spaceName = None 23 | sex = None 24 | rank = None 25 | avatar = None 26 | follow = None#关注好友数目 27 | fans = None#粉丝数目 28 | article = None#投稿数 29 | place = None#所在地 30 | description = None#认证用户为认证信息 普通用户为交友宣言 31 | followlist = None#关注的好友列表 32 | friend = None 33 | DisplayRank = None 34 | 35 | 36 | class Video(): 37 | def __init__(self,m_aid=None,m_title=None): 38 | if m_aid: 39 | self.aid = m_aid 40 | if m_title: 41 | if isinstance(m_title,unicode): 42 | m_title = m_title.encode('utf8') 43 | self.title = m_title 44 | aid = None 45 | title = None 46 | guankan = None 47 | shoucang = None 48 | danmu = None 49 | date = None 50 | cover = None 51 | commentNumber = None 52 | description = None 53 | tag = None 54 | author = None 55 | page = None 56 | credit = None 57 | coin = None 58 | spid = None 59 | cid = None 60 | offsite = None#Flash播放调用地址 61 | Iscopy = None 62 | subtitle = None 63 | duration = None 64 | episode = None 65 | arcurl = None#网页地址 66 | arcrank = None#不明 67 | #不明: 68 | tid = None 69 | typename = None 70 | instant_server = None 71 | src = None 72 | partname = None 73 | allow_bp = None 74 | allow_feed = None 75 | created = None 76 | #播放信息: 77 | play_site = None 78 | play_forward = None 79 | play_mobile = None 80 | 81 | class Bangumi(): 82 | def __init__(self): 83 | pass 84 | typeid = None 85 | lastupdate = None 86 | areaid = None 87 | bgmcount = None#番剧当前总集数 88 | title = None 89 | lastupdate_at = None 90 | attention = None #订阅数 91 | cover = None 92 | priority = None 93 | area = None 94 | weekday = None 95 | spid = None 96 | new = None 97 | scover = None 98 | mcover = None 99 | click = None 100 | season_id = None 101 | click = None # 浏览数 102 | video_view = None 103 | 104 | class Comment(): 105 | def __init__(self): 106 | self.post_user = User() 107 | lv = None#楼层 108 | fbid = None#评论id 109 | msg = None 110 | ad_check = None#状态 (0: 正常 1: UP主隐藏 2: 管理员删除 3: 因举报删除) 111 | post_user = None 112 | 113 | class CommentList(): 114 | def __init__(self): 115 | pass 116 | comments = None 117 | commentLen = None 118 | page = None 119 | 120 | class ZhuantiInfo(): 121 | def __init__(self, m_spid,m_title): 122 | self.spid = m_spid 123 | if isinstance(m_title,unicode): 124 | m_title = m_title.encode('utf8') 125 | self.title = m_title 126 | spid = None 127 | title = None 128 | author = None 129 | cover = None 130 | thumb = None 131 | ischeck = None #不明 132 | typeurl = None #总是"http://www.bilibili.com" 133 | tag = None 134 | description = None 135 | pubdate = None # 不明 136 | postdate = None 137 | lastupdate = None 138 | click = None 139 | favourite = None 140 | attention = None 141 | count = None 142 | bgmcount = None 143 | spcount = None 144 | season_id = None 145 | is_bangumi = None 146 | arcurl = None 147 | 148 | class Danmu(): 149 | def __init__(self): 150 | pass 151 | t_video = None 152 | t_stamp = None 153 | mid_crc = None # 值为:hex(binascii.crc32(mid)) 154 | danmu_type = None # 1:滚动弹幕 5:顶端弹幕 4:底部弹幕 155 | content = None 156 | danmu_color = None -------------------------------------------------------------------------------- /BilibiliSearch/Source/bilibiliSearch.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import sys 4 | from Feedback import * 5 | from support import * 6 | import datetime 7 | import hashlib 8 | 9 | def biliVideoSearch(appkey, AppSecret, keyword, order = 'default', pagesize = 20, page = 1): 10 | """ 11 | 【注】: 12 | 旧版Appkey不可用,必须配合AppSecret使用!! 13 | 14 | 根据关键词搜索视频 15 | 输入: 16 | order:排序方式 默认default,其余待测试 17 | keyword:关键词 18 | pagesize:返回条目多少 19 | page:页码 20 | """ 21 | paras = {} 22 | paras['keyword'] = GetString(keyword) 23 | url = 'http://api.bilibili.cn/search?' + GetSign(paras, appkey, AppSecret) 24 | jsoninfo = JsonInfo(url) 25 | videolist = [] 26 | for video_idx in jsoninfo.Getvalue('result'): 27 | if video_idx['type'] != 'video': 28 | continue 29 | video_idx = DictDecode2UTF8(video_idx) 30 | video = Video(video_idx['aid'], video_idx['title']) 31 | video.typename = video_idx['typename'] 32 | video.author = User(video_idx['mid'], video_idx['author']) 33 | video.acurl = video_idx['arcurl'] 34 | video.description = video_idx['description'] 35 | video.arcrank = video_idx['arcrank'] 36 | video.cover = video_idx['pic'] 37 | video.guankan = video_idx['play'] 38 | video.danmu = video_idx['video_review'] 39 | video.shoucang = video_idx['favorites'] 40 | video.commentNumber = video_idx['review'] 41 | video.date = video_idx['pubdate'] 42 | video.tag = video_idx['tag'].split(',') 43 | videolist.append(video) 44 | return videolist 45 | 46 | query = '{query}' 47 | appkey = '70472776da900153' 48 | secretkey = 'f7d9146f9363f3407d31098918493336' 49 | videoList = biliVideoSearch(appkey,secretkey,query, pagesize = 30) 50 | fb = Feedback() 51 | 52 | try: 53 | for video in videoList: 54 | fb.add_item(video.title,subtitle="%s : http://www.bilibili.com/video/%s(%s)"%(video.typename,video.aid,datetime.datetime.utcfromtimestamp(video.date).strftime(r"%Y/%m/%d")),arg=video.aid) 55 | 56 | except SyntaxError as e: 57 | if ('EOF', 'EOL' in e.msg): 58 | fb.add_item('...') 59 | else: 60 | fb.add_item('SyntaxError', e.msg) 61 | except Exception as e: 62 | fb.add_item(e.__class__.__name__,subtitle=e.message) 63 | print fb 64 | -------------------------------------------------------------------------------- /BilibiliSearch/Source/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vespa314/BilibiliAlfredWorkFlows/43f3a27d35bd28118bf9935d4236344b2d5d86d7/BilibiliSearch/Source/icon.png -------------------------------------------------------------------------------- /BilibiliSearch/Source/info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | bundleid 6 | kylen314.alfred.bilibilisearch 7 | category 8 | Internet 9 | connections 10 | 11 | 30C75941-C36F-4C44-9222-52F3ACFE774C 12 | 13 | 14 | destinationuid 15 | 31F3C277-AF07-4A8D-934C-5511F56CD535 16 | modifiers 17 | 0 18 | modifiersubtext 19 | 20 | 21 | 22 | destinationuid 23 | 6A7F63C1-458D-4214-8ABE-4C40319449A4 24 | modifiers 25 | 1048576 26 | modifiersubtext 27 | 直接观看 28 | 29 | 30 | destinationuid 31 | 59B114EB-6594-4929-9A2B-C723962F996F 32 | modifiers 33 | 262144 34 | modifiersubtext 35 | 高清播放 36 | 37 | 38 | destinationuid 39 | 0E7DD81D-EAED-4607-98EB-11865706F61F 40 | modifiers 41 | 524288 42 | modifiersubtext 43 | 下载视频 44 | 45 | 46 | destinationuid 47 | D8A0BE2A-23E4-4D8C-A32A-24FCFA5AC2C6 48 | modifiers 49 | 131072 50 | modifiersubtext 51 | 下载弹幕到桌面 52 | 53 | 54 | D8A0BE2A-23E4-4D8C-A32A-24FCFA5AC2C6 55 | 56 | 57 | destinationuid 58 | 0B676FD4-D8AF-436B-94C2-4F85D28E6379 59 | modifiers 60 | 0 61 | modifiersubtext 62 | 63 | 64 | 65 | 66 | createdby 67 | Vespa 68 | description 69 | B站查询 70 | disabled 71 | 72 | name 73 | bilibili 74 | objects 75 | 76 | 77 | config 78 | 79 | plusspaces 80 | 81 | url 82 | http://www.bilibili.com/video/av{query}/ 83 | utf8 84 | 85 | 86 | type 87 | alfred.workflow.action.openurl 88 | uid 89 | 31F3C277-AF07-4A8D-934C-5511F56CD535 90 | version 91 | 0 92 | 93 | 94 | config 95 | 96 | argumenttype 97 | 0 98 | escaping 99 | 36 100 | keyword 101 | bl 102 | queuedelaycustom 103 | 1 104 | queuedelayimmediatelyinitially 105 | 106 | queuedelaymode 107 | 0 108 | queuemode 109 | 1 110 | runningsubtext 111 | 少女祈祷中 112 | script 113 | # -*- coding: utf-8 -*- 114 | 115 | import sys 116 | from Feedback import * 117 | from support import * 118 | import datetime 119 | import hashlib 120 | 121 | def GetSign(params, appkey, AppSecret=None): 122 | """ 123 | 获取新版API的签名,不然会返回-3错误 124 | 待添加:【重要!】 125 | 需要做URL编码并保证字母都是大写,如 %2F 126 | """ 127 | params['appkey']=appkey 128 | data = "" 129 | paras = params.keys() 130 | paras.sort() 131 | for para in paras: 132 | if data != "": 133 | data += "&" 134 | data += para + "=" + str(params[para]) 135 | if AppSecret == None: 136 | return data 137 | m = hashlib.md5() 138 | m.update(data+AppSecret) 139 | return data+'&sign='+m.hexdigest() 140 | 141 | def biliVideoSearch(appkey, AppSecret, keyword, order = 'default', pagesize = 20, page = 1): 142 | """ 143 | 【注】: 144 | 旧版Appkey不可用,必须配合AppSecret使用!! 145 | 146 | 根据关键词搜索视频 147 | 输入: 148 | order:排序方式 默认default,其余待测试 149 | keyword:关键词 150 | pagesize:返回条目多少 151 | page:页码 152 | """ 153 | paras = {} 154 | paras['keyword'] = GetString(keyword) 155 | paras['order'] = GetString(order) 156 | paras['pagesize'] = GetString(pagesize) 157 | paras['page'] = GetString(page) 158 | url = 'http://api.bilibili.cn/search?' + GetSign(paras, appkey, AppSecret) 159 | jsoninfo = JsonInfo(url) 160 | videolist = [] 161 | for video_idx in jsoninfo.Getvalue('result'): 162 | if video_idx['type'] != 'video': 163 | continue 164 | video = Video(video_idx['aid'], video_idx['title']) 165 | video.typename = video_idx['typename'] 166 | video.author = User(video_idx['mid'], video_idx['author']) 167 | video.acurl = video_idx['arcurl'] 168 | video.description = video_idx['description'] 169 | video.arcrank = video_idx['arcrank'] 170 | video.cover = video_idx['pic'] 171 | video.guankan = video_idx['play'] 172 | video.danmu = video_idx['video_review'] 173 | video.shoucang = video_idx['favorites'] 174 | video.commentNumber = video_idx['review'] 175 | video.date = video_idx['pubdate'] 176 | video.tag = video_idx['tag'].split(',') 177 | videolist.append(video) 178 | return videolist 179 | 180 | query = '{query}' 181 | appkey = '70472776da900153' 182 | secretkey = 'f7d9146f9363f3407d31098918493336' 183 | videoList = biliVideoSearch(appkey,secretkey,query, pagesize = 30) 184 | fb = Feedback() 185 | 186 | try: 187 | for video in videoList: 188 | fb.add_item(video.title,subtitle="%s : http://www.bilibili.com/video/%s(%s)"%(video.typename,video.aid,datetime.datetime.utcfromtimestamp(video.date).strftime(r"%Y/%m/%d")),arg=video.aid) 189 | 190 | except SyntaxError as e: 191 | if ('EOF', 'EOL' in e.msg): 192 | fb.add_item('...') 193 | else: 194 | fb.add_item('SyntaxError', e.msg) 195 | except Exception as e: 196 | fb.add_item(e.__class__.__name__,subtitle=e.message) 197 | print fb 198 | 199 | subtext 200 | B站查询 201 | title 202 | Search in Bilibili 203 | type 204 | 3 205 | withspace 206 | 207 | 208 | type 209 | alfred.workflow.input.scriptfilter 210 | uid 211 | 30C75941-C36F-4C44-9222-52F3ACFE774C 212 | version 213 | 0 214 | 215 | 216 | config 217 | 218 | escaping 219 | 0 220 | script 221 | bili http://www.bilibili.com/video/av{query}/ 222 | 223 | type 224 | alfred.workflow.action.terminalcommand 225 | uid 226 | 6A7F63C1-458D-4214-8ABE-4C40319449A4 227 | version 228 | 0 229 | 230 | 231 | config 232 | 233 | escaping 234 | 0 235 | script 236 | bili --hd http://www.bilibili.com/video/av{query}/ 237 | 238 | type 239 | alfred.workflow.action.terminalcommand 240 | uid 241 | 59B114EB-6594-4929-9A2B-C723962F996F 242 | version 243 | 0 244 | 245 | 246 | config 247 | 248 | escaping 249 | 0 250 | script 251 | cd ~/Desktop 252 | you-get http://www.bilibili.com/video/av{query}/ 253 | 254 | type 255 | alfred.workflow.action.terminalcommand 256 | uid 257 | 0E7DD81D-EAED-4607-98EB-11865706F61F 258 | version 259 | 0 260 | 261 | 262 | config 263 | 264 | concurrently 265 | 266 | escaping 267 | 127 268 | script 269 | # -*- coding: utf-8 -*- 270 | """ 271 | Created on Mon May 26 23:42:03 2014 272 | 273 | @author: Administrator 274 | """ 275 | 276 | 277 | from support import * 278 | import hashlib 279 | import io 280 | import xml.dom.minidom 281 | import random 282 | import math 283 | import os 284 | import sys 285 | 286 | default_encoding = 'utf-8' 287 | if sys.getdefaultencoding() != default_encoding: 288 | reload(sys) 289 | sys.setdefaultencoding(default_encoding) 290 | 291 | class safe_list(list): 292 | def get(self, index, default=None): 293 | try: 294 | return self[index] 295 | except IndexError: 296 | return default 297 | 298 | # Calculation is based on https://github.com/jabbany/CommentCoreLibrary/issues/5#issuecomment-40087282 299 | # and https://github.com/m13253/danmaku2ass/issues/7#issuecomment-41489422 300 | # ASS FOV = width*4/3.0 301 | # But Flash FOV = width/math.tan(100*math.pi/360.0)/2 will be used instead 302 | # Result: (transX, transY, rotX, rotY, rotZ, scaleX, scaleY) 303 | def ConvertFlashRotation(rotY, rotZ, X, Y, width, height): 304 | def WrapAngle(deg): 305 | return 180-((180-deg) % 360) 306 | rotY = WrapAngle(rotY) 307 | rotZ = WrapAngle(rotZ) 308 | if rotY in (90, -90): 309 | rotY -= 1 310 | if rotY == 0 or rotZ == 0: 311 | outX = 0 312 | outY = -rotY # Positive value means clockwise in Flash 313 | outZ = -rotZ 314 | rotY *= math.pi/180.0 315 | rotZ *= math.pi/180.0 316 | else: 317 | rotY *= math.pi/180.0 318 | rotZ *= math.pi/180.0 319 | outY = math.atan2(-math.sin(rotY)*math.cos(rotZ), math.cos(rotY))*180/math.pi 320 | outZ = math.atan2(-math.cos(rotY)*math.sin(rotZ), math.cos(rotZ))*180/math.pi 321 | outX = math.asin(math.sin(rotY)*math.sin(rotZ))*180/math.pi 322 | trX = (X*math.cos(rotZ)+Y*math.sin(rotZ))/math.cos(rotY)+(1-math.cos(rotZ)/math.cos(rotY))*width/2-math.sin(rotZ)/math.cos(rotY)*height/2 323 | trY = Y*math.cos(rotZ)-X*math.sin(rotZ)+math.sin(rotZ)*width/2+(1-math.cos(rotZ))*height/2 324 | trZ = (trX-width/2)*math.sin(rotY) 325 | FOV = width*math.tan(2*math.pi/9.0)/2 326 | try: 327 | scaleXY = FOV/(FOV+trZ) 328 | except ZeroDivisionError: 329 | logging.error('Rotation makes object behind the camera: trZ == %.0f' % trZ) 330 | scaleXY = 1 331 | trX = (trX-width/2)*scaleXY+width/2 332 | trY = (trY-height/2)*scaleXY+height/2 333 | if scaleXY < 0: 334 | scaleXY = -scaleXY 335 | outX += 180 336 | outY += 180 337 | logging.error('Rotation makes object behind the camera: trZ == %.0f < %.0f' % (trZ, FOV)) 338 | return (trX, trY, WrapAngle(outX), WrapAngle(outY), WrapAngle(outZ), scaleXY*100, scaleXY*100) 339 | 340 | 341 | def WriteCommentBilibiliPositioned(f, c, width, height, styleid): 342 | #BiliPlayerSize = (512, 384) # Bilibili player version 2010 343 | #BiliPlayerSize = (540, 384) # Bilibili player version 2012 344 | BiliPlayerSize = (672, 438) # Bilibili player version 2014 345 | ZoomFactor = GetZoomFactor(BiliPlayerSize, (width, height)) 346 | 347 | def GetPosition(InputPos, isHeight): 348 | isHeight = int(isHeight) # True -> 1 349 | if isinstance(InputPos, int): 350 | return ZoomFactor[0]*InputPos+ZoomFactor[isHeight+1] 351 | elif isinstance(InputPos, float): 352 | if InputPos > 1: 353 | return ZoomFactor[0]*InputPos+ZoomFactor[isHeight+1] 354 | else: 355 | return BiliPlayerSize[isHeight]*ZoomFactor[0]*InputPos+ZoomFactor[isHeight+1] 356 | else: 357 | try: 358 | InputPos = int(InputPos) 359 | except ValueError: 360 | InputPos = float(InputPos) 361 | return GetPosition(InputPos, isHeight) 362 | 363 | try: 364 | comment_args = safe_list(json.loads(c[3])) 365 | text = ASSEscape(str(comment_args[4]).replace('/n', '\n')) 366 | from_x = comment_args.get(0, 0) 367 | from_y = comment_args.get(1, 0) 368 | to_x = comment_args.get(7, from_x) 369 | to_y = comment_args.get(8, from_y) 370 | from_x = GetPosition(from_x, False) 371 | from_y = GetPosition(from_y, True) 372 | to_x = GetPosition(to_x, False) 373 | to_y = GetPosition(to_y, True) 374 | alpha = safe_list(str(comment_args.get(2, '1')).split('-')) 375 | from_alpha = float(alpha.get(0, 1)) 376 | to_alpha = float(alpha.get(1, from_alpha)) 377 | from_alpha = 255-round(from_alpha*255) 378 | to_alpha = 255-round(to_alpha*255) 379 | rotate_z = int(comment_args.get(5, 0)) 380 | rotate_y = int(comment_args.get(6, 0)) 381 | lifetime = float(comment_args.get(3, 4500)) 382 | duration = int(comment_args.get(9, lifetime*1000)) 383 | delay = int(comment_args.get(10, 0)) 384 | fontface = comment_args.get(12) 385 | isborder = comment_args.get(11, 'true') 386 | from_rotarg = ConvertFlashRotation(rotate_y, rotate_z, from_x, from_y, width, height) 387 | to_rotarg = ConvertFlashRotation(rotate_y, rotate_z, to_x, to_y, width, height) 388 | styles = ['\\org(%d, %d)' % (width/2, height/2)] 389 | if from_rotarg[0:2] == to_rotarg[0:2]: 390 | styles.append('\\pos(%.0f, %.0f)' % (from_rotarg[0:2])) 391 | else: 392 | styles.append('\\move(%.0f, %.0f, %.0f, %.0f, %.0f, %.0f)' % (from_rotarg[0:2]+to_rotarg[0:2]+(delay, delay+duration))) 393 | styles.append('\\frx%.0f\\fry%.0f\\frz%.0f\\fscx%.0f\\fscy%.0f' % (from_rotarg[2:7])) 394 | if (from_x, from_y) != (to_x, to_y): 395 | styles.append('\\t(%d, %d, ' % (delay, delay+duration)) 396 | styles.append('\\frx%.0f\\fry%.0f\\frz%.0f\\fscx%.0f\\fscy%.0f' % (to_rotarg[2:7])) 397 | styles.append(')') 398 | if fontface: 399 | styles.append('\\fn%s' % ASSEscape(fontface)) 400 | styles.append('\\fs%.0f' % (c[6]*ZoomFactor[0])) 401 | if c[5] != 0xffffff: 402 | styles.append('\\c&H%s&' % ConvertColor(c[5])) 403 | if c[5] == 0x000000: 404 | styles.append('\\3c&HFFFFFF&') 405 | if from_alpha == to_alpha: 406 | styles.append('\\alpha&H%02X' % from_alpha) 407 | elif (from_alpha, to_alpha) == (255, 0): 408 | styles.append('\\fad(%.0f,0)' % (lifetime*1000)) 409 | elif (from_alpha, to_alpha) == (0, 255): 410 | styles.append('\\fad(0, %.0f)' % (lifetime*1000)) 411 | else: 412 | styles.append('\\fade(%(from_alpha)d, %(to_alpha)d, %(to_alpha)d, 0, %(end_time).0f, %(end_time).0f, %(end_time).0f)' % {'from_alpha': from_alpha, 'to_alpha': to_alpha, 'end_time': lifetime*1000}) 413 | if isborder == 'false': 414 | styles.append('\\bord0') 415 | f.write('Dialogue: -1,%(start)s,%(end)s,%(styleid)s,,0,0,0,,{%(styles)s}%(text)s\n' % {'start': ConvertTimestamp(c[0]), 'end': ConvertTimestamp(c[0]+lifetime), 'styles': ''.join(styles), 'text': text, 'styleid': styleid}) 416 | except (IndexError, ValueError) as e: 417 | try: 418 | logging.warning(_('Invalid comment: %r') % c[3]) 419 | except IndexError: 420 | logging.warning(_('Invalid comment: %r') % c) 421 | 422 | # Result: (f, dx, dy) 423 | # To convert: NewX = f*x+dx, NewY = f*y+dy 424 | def GetZoomFactor(SourceSize, TargetSize): 425 | try: 426 | if (SourceSize, TargetSize) == GetZoomFactor.Cached_Size: 427 | return GetZoomFactor.Cached_Result 428 | except AttributeError: 429 | pass 430 | GetZoomFactor.Cached_Size = (SourceSize, TargetSize) 431 | try: 432 | SourceAspect = SourceSize[0]/SourceSize[1] 433 | TargetAspect = TargetSize[0]/TargetSize[1] 434 | if TargetAspect < SourceAspect: # narrower 435 | ScaleFactor = TargetSize[0]/SourceSize[0] 436 | GetZoomFactor.Cached_Result = (ScaleFactor, 0, (TargetSize[1]-TargetSize[0]/SourceAspect)/2) 437 | elif TargetAspect > SourceAspect: # wider 438 | ScaleFactor = TargetSize[1]/SourceSize[1] 439 | GetZoomFactor.Cached_Result = (ScaleFactor, (TargetSize[0]-TargetSize[1]*SourceAspect)/2, 0) 440 | else: 441 | GetZoomFactor.Cached_Result = (TargetSize[0]/SourceSize[0], 0, 0) 442 | return GetZoomFactor.Cached_Result 443 | except ZeroDivisionError: 444 | GetZoomFactor.Cached_Result = (1, 0, 0) 445 | return GetZoomFactor.Cached_Result 446 | 447 | 448 | def WriteASSHead(f, width, height, fontface, fontsize, alpha, styleid): 449 | f.write( 450 | ''' 451 | [Script Info] 452 | ; Script generated by Danmaku2ASS 453 | ; https://github.com/m13253/danmaku2ass 454 | Script Updated By: Danmaku2ASS (https://github.com/m13253/danmaku2ass) 455 | ScriptType: v4.00+ 456 | PlayResX: %(width)d 457 | PlayResY: %(height)d 458 | Aspect Ratio: %(width)d:%(height)d 459 | Collisions: Normal 460 | WrapStyle: 2 461 | ScaledBorderAndShadow: yes 462 | YCbCr Matrix: TV.601 463 | 464 | [V4+ Styles] 465 | Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding 466 | Style: %(styleid)s, %(fontface)s, %(fontsize).0f, &H%(alpha)02XFFFFFF, &H%(alpha)02XFFFFFF, &H%(alpha)02X000000, &H%(alpha)02X000000, 0, 0, 0, 0, 100, 100, 0.00, 0.00, 1, %(outline).0f, 0, 7, 0, 0, 0, 0 467 | 468 | [Events] 469 | Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text 470 | ''' % {'width': width, 'height': height, 'fontface': fontface, 'fontsize': fontsize, 'alpha': 255-round(alpha*255), 'outline': max(fontsize/25.0, 1), 'styleid': styleid} 471 | ) 472 | 473 | def TestFreeRows(rows, c, row, width, height, bottomReserved, lifetime): 474 | res = 0 475 | rowmax = height-bottomReserved 476 | targetRow = None 477 | if c[4] in (1, 2): 478 | while row < rowmax and res < c[7]: 479 | if targetRow != rows[c[4]][row]: 480 | targetRow = rows[c[4]][row] 481 | if targetRow and targetRow[0]+lifetime > c[0]: 482 | break 483 | row += 1 484 | res += 1 485 | else: 486 | try: 487 | thresholdTime = c[0]-lifetime*(1-width/(c[8]+width)) 488 | except ZeroDivisionError: 489 | thresholdTime = c[0]-lifetime 490 | while row < rowmax and res < c[7]: 491 | if targetRow != rows[c[4]][row]: 492 | targetRow = rows[c[4]][row] 493 | try: 494 | if targetRow and (targetRow[0] > thresholdTime or targetRow[0]+targetRow[8]*lifetime/(targetRow[8]+width) > c[0]): 495 | break 496 | except ZeroDivisionError: 497 | pass 498 | row += 1 499 | res += 1 500 | return res 501 | 502 | def MarkCommentRow(rows, c, row): 503 | row = int(row) 504 | try: 505 | for i in range(row, int(row+math.ceil(c[7]))): 506 | rows[c[4]][i] = c 507 | except IndexError: 508 | pass 509 | 510 | def ASSEscape(s): 511 | def ReplaceLeadingSpace(s): 512 | sstrip = s.strip(' ') 513 | slen = len(s) 514 | if slen == len(sstrip): 515 | return s 516 | else: 517 | llen = slen-len(s.lstrip(' ')) 518 | rlen = slen-len(s.rstrip(' ')) 519 | return ''.join(('\u2007'*llen, sstrip, '\u2007'*rlen)) 520 | return '\\N'.join((ReplaceLeadingSpace(i) or ' ' for i in str(s).replace('\\', '\\\\').replace('{', '\\{').replace('}', '\\}').split('\n'))) 521 | 522 | def ConvertTimestamp(timestamp): 523 | timestamp = round(timestamp*100.0) 524 | hour, minute = divmod(timestamp, 360000) 525 | minute, second = divmod(minute, 6000) 526 | second, centsecond = divmod(second, 100) 527 | return '%d:%02d:%02d.%02d' % (int(hour), int(minute), int(second), int(centsecond)) 528 | 529 | def ConvertType2(row, height, bottomReserved): 530 | return height-bottomReserved-row 531 | 532 | def FindAlternativeRow(rows, c, height, bottomReserved): 533 | res = 0 534 | for row in range(int(height-bottomReserved-math.ceil(c[7]))): 535 | if not rows[c[4]][row]: 536 | return row 537 | elif rows[c[4]][row][0] < rows[c[4]][res][0]: 538 | res = row 539 | return res 540 | 541 | def ConvertColor(RGB, width=1280, height=576): 542 | if RGB == 0x000000: 543 | return '000000' 544 | elif RGB == 0xffffff: 545 | return 'FFFFFF' 546 | R = (RGB >> 16) & 0xff 547 | G = (RGB >> 8) & 0xff 548 | B = RGB & 0xff 549 | if width < 1280 and height < 576: 550 | return '%02X%02X%02X' % (B, G, R) 551 | else: # VobSub always uses BT.601 colorspace, convert to BT.709 552 | ClipByte = lambda x: 255 if x > 255 else 0 if x < 0 else round(x) 553 | return '%02X%02X%02X' % ( 554 | ClipByte(R*0.00956384088080656+G*0.03217254540203729+B*0.95826361371715607), 555 | ClipByte(R*-0.10493933142075390+G*1.17231478191855154+B*-0.06737545049779757), 556 | ClipByte(R*0.91348912373987645+G*0.07858536372532510+B*0.00792551253479842) 557 | ) 558 | 559 | def WriteComment(f, c, row, width, height, bottomReserved, fontsize, lifetime, styleid): 560 | text = ASSEscape(c[3]) 561 | styles = [] 562 | if c[4] == 1: 563 | styles.append('\\an8\\pos(%(halfwidth)d, %(row)d)' % {'halfwidth': width/2, 'row': row}) 564 | elif c[4] == 2: 565 | styles.append('\\an2\\pos(%(halfwidth)d, %(row)d)' % {'halfwidth': width/2, 'row': ConvertType2(row, height, bottomReserved)}) 566 | elif c[4] == 3: 567 | styles.append('\\move(%(neglen)d, %(row)d, %(width)d, %(row)d)' % {'width': width, 'row': row, 'neglen': -math.ceil(c[8])}) 568 | else: 569 | styles.append('\\move(%(width)d, %(row)d, %(neglen)d, %(row)d)' % {'width': width, 'row': row, 'neglen': -math.ceil(c[8])}) 570 | if not (-1 < c[6]-fontsize < 1): 571 | styles.append('\\fs%.0f' % c[6]) 572 | if c[5] != 0xffffff: 573 | styles.append('\\c&H%s&' % ConvertColor(c[5])) 574 | if c[5] == 0x000000: 575 | styles.append('\\3c&HFFFFFF&') 576 | f.write('Dialogue: 2,%(start)s,%(end)s,%(styleid)s,,0000,0000,0000,,{%(styles)s}%(text)s\n' % {'start': ConvertTimestamp(c[0]), 'end': ConvertTimestamp(c[0]+lifetime), 'styles': ''.join(styles), 'text': text, 'styleid': styleid}) 577 | 578 | 579 | def CalculateLength(s): 580 | return max(map(len, s.split('\n'))) # May not be accurate 581 | 582 | def GetVedioInfo(aid,appkey,page = 1,AppSecret=None,fav = None): 583 | paras = {'id': GetString(aid),'page': GetString(page)}; 584 | if fav != None: 585 | paras['fav'] = fav; 586 | url = 'http://api.bilibili.cn/view?'+GetSign(paras,appkey,AppSecret); 587 | jsoninfo = JsonInfo(url); 588 | vedio = Vedio(aid,jsoninfo.Getvalue('title')); 589 | vedio.guankan = jsoninfo.Getvalue('play') 590 | vedio.commentNumber = jsoninfo.Getvalue('review') 591 | vedio.danmu = jsoninfo.Getvalue('video_review') 592 | vedio.shoucang = jsoninfo.Getvalue('favorites'); 593 | vedio.description = jsoninfo.Getvalue('description') 594 | vedio.tag = []; 595 | taglist = jsoninfo.Getvalue('tag'); 596 | if taglist != None: 597 | for tag in taglist.split(','): 598 | vedio.tag.append(tag); 599 | vedio.cover = jsoninfo.Getvalue('pic'); 600 | vedio.author = User(jsoninfo.Getvalue('mid'),jsoninfo.Getvalue('author')); 601 | vedio.page = jsoninfo.Getvalue('pages'); 602 | vedio.date = jsoninfo.Getvalue('created_at'); 603 | vedio.credit = jsoninfo.Getvalue('credit'); 604 | vedio.coin = jsoninfo.Getvalue('coins'); 605 | vedio.spid = jsoninfo.Getvalue('spid'); 606 | vedio.cid = jsoninfo.Getvalue('cid'); 607 | vedio.offsite = jsoninfo.Getvalue('offsite'); 608 | vedio.partname = jsoninfo.Getvalue('partname'); 609 | vedio.src = jsoninfo.Getvalue('src'); 610 | vedio.tid = jsoninfo.Getvalue('tid') 611 | vedio.typename = jsoninfo.Getvalue('typename') 612 | vedio.instant_server = jsoninfo.Getvalue('instant_server'); 613 | return vedio 614 | 615 | def GetSign(params,appkey,AppSecret=None): 616 | """ 617 | 获取新版API的签名,不然会返回-3错误 618 | 待添加:【重要!】 619 | 需要做URL编码并保证字母都是大写,如 %2F 620 | """ 621 | params['appkey']=appkey; 622 | data = ""; 623 | paras = params.keys(); 624 | paras.sort(); 625 | for para in paras: 626 | if data != "": 627 | data += "&"; 628 | data += para + "=" + params[para]; 629 | if AppSecret == None: 630 | return data 631 | m = hashlib.md5() 632 | m.update(data+AppSecret) 633 | return data+'&sign='+m.hexdigest() 634 | 635 | 636 | 637 | 638 | def GetDanmuku(cid): 639 | cid = getint(cid); 640 | url = "http://comment.bilibili.cn/%d.xml"%(cid); 641 | content = zlib.decompressobj(-zlib.MAX_WBITS).decompress(getURLContent(url)) 642 | # content = GetRE(content,r'<d p=[^>]*>([^<]*)<') 643 | return content; 644 | 645 | 646 | #def FilterBadChars(f): 647 | # s = f.read() 648 | # s = re.sub('[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f]', '\ufffd', s) 649 | # return io.StringIO(s) 650 | 651 | def ReadCommentsBilibili(f, fontsize): 652 | dom = xml.dom.minidom.parseString(f) 653 | comment_element = dom.getElementsByTagName('d') 654 | for i, comment in enumerate(comment_element): 655 | try: 656 | p = str(comment.getAttribute('p')).split(',') 657 | assert len(p) >= 5 658 | assert p[1] in ('1', '4', '5', '6', '7') 659 | if p[1] != '7': 660 | c = str(comment.childNodes[0].wholeText).replace('/n', '\n') 661 | size = int(p[2])*fontsize/25.0 662 | yield (float(p[0]), int(p[4]), i, c, {'1': 0, '4': 2, '5': 1, '6': 3}[p[1]], int(p[3]), size, (c.count('\n')+1)*size, CalculateLength(c)*size) 663 | else: # positioned comment 664 | c = str(comment.childNodes[0].wholeText) 665 | yield (float(p[0]), int(p[4]), i, c, 'bilipos', int(p[3]), int(p[2]), 0, 0) 666 | except (AssertionError, AttributeError, IndexError, TypeError, ValueError): 667 | continue 668 | 669 | def ConvertToFile(filename_or_file, *args, **kwargs): 670 | return open(filename_or_file, *args, **kwargs) 671 | 672 | 673 | def ProcessComments(comments, f, width, height, bottomReserved, fontface, fontsize, alpha, lifetime, reduced, progress_callback): 674 | styleid = 'Danmaku2ASS_%04x' % random.randint(0, 0xffff) 675 | WriteASSHead(f, width, height, fontface, fontsize, alpha, styleid) 676 | rows = [[None]*(height-bottomReserved+1) for i in range(4)] 677 | for idx, i in enumerate(comments): 678 | if progress_callback and idx % 1000 == 0: 679 | progress_callback(idx, len(comments)) 680 | if isinstance(i[4], int): 681 | row = 0 682 | rowmax = height-bottomReserved-i[7] 683 | while row <= rowmax: 684 | freerows = TestFreeRows(rows, i, row, width, height, bottomReserved, lifetime) 685 | if freerows >= i[7]: 686 | MarkCommentRow(rows, i, row) 687 | WriteComment(f, i, row, width, height, bottomReserved, fontsize, lifetime, styleid) 688 | break 689 | else: 690 | row += freerows or 1 691 | else: 692 | if not reduced: 693 | row = FindAlternativeRow(rows, i, height, bottomReserved) 694 | MarkCommentRow(rows, i, row) 695 | WriteComment(f, i, row, width, height, bottomReserved, fontsize, lifetime, styleid) 696 | elif i[4] == 'bilipos': 697 | WriteCommentBilibiliPositioned(f, i, width, height, styleid) 698 | elif i[4] == 'acfunpos': 699 | WriteCommentAcfunPositioned(f, i, width, height, styleid) 700 | elif i[4] == 'sH5Vpos': 701 | WriteCommentSH5VPositioned(f, i, width, height, styleid) 702 | else: 703 | logging.warning(_('Invalid comment: %r') % i[3]) 704 | if progress_callback: 705 | progress_callback(len(comments), len(comments)) 706 | 707 | def Danmaku2ASS(input_files, output_file, stage_width, stage_height, reserve_blank=0, font_face='sans-serif', font_size=25.0, text_opacity=1.0, comment_duration=5.0, is_reduce_comments=False, progress_callback=None): 708 | fo = None 709 | comments = ReadComments(input_files, font_size) 710 | try: 711 | fo = ConvertToFile(output_file, 'w') 712 | ProcessComments(comments, fo, stage_width, stage_height, reserve_blank, font_face, font_size, text_opacity, comment_duration, is_reduce_comments, progress_callback) 713 | finally: 714 | if output_file and fo != output_file: 715 | fo.close() 716 | 717 | def ReadComments(input_files, font_size=25.0): 718 | comments = [] 719 | comments.extend(ReadCommentsBilibili(input_files, font_size)) 720 | comments.sort() 721 | return comments 722 | 723 | av = '{query}' 724 | appkey = "03fc8eb101b091fb" 725 | vedio = GetVedioInfo(av ,appkey,AppSecret=None) 726 | Danmaku2ASS(GetDanmuku(vedio.cid),r'%s/Desktop/%s.ass'%(os.path.expanduser('~'),vedio.title), 640, 360, 0, 'sans-serif', 15, 0.5, 10, False) 727 | type 728 | 3 729 | 730 | type 731 | alfred.workflow.action.script 732 | uid 733 | D8A0BE2A-23E4-4D8C-A32A-24FCFA5AC2C6 734 | version 735 | 0 736 | 737 | 738 | config 739 | 740 | lastpathcomponent 741 | 742 | onlyshowifquerypopulated 743 | 744 | output 745 | 0 746 | removeextension 747 | 748 | sticky 749 | 750 | title 751 | 弹幕文件已下载到桌面 752 | 753 | type 754 | alfred.workflow.output.notification 755 | uid 756 | 0B676FD4-D8AF-436B-94C2-4F85D28E6379 757 | version 758 | 0 759 | 760 | 761 | readme 762 | 763 | uidata 764 | 765 | 0B676FD4-D8AF-436B-94C2-4F85D28E6379 766 | 767 | ypos 768 | 470 769 | 770 | 0E7DD81D-EAED-4607-98EB-11865706F61F 771 | 772 | ypos 773 | 330 774 | 775 | 30C75941-C36F-4C44-9222-52F3ACFE774C 776 | 777 | ypos 778 | 110 779 | 780 | 31F3C277-AF07-4A8D-934C-5511F56CD535 781 | 782 | ypos 783 | 10 784 | 785 | 59B114EB-6594-4929-9A2B-C723962F996F 786 | 787 | ypos 788 | 220 789 | 790 | 6A7F63C1-458D-4214-8ABE-4C40319449A4 791 | 792 | ypos 793 | 120 794 | 795 | D8A0BE2A-23E4-4D8C-A32A-24FCFA5AC2C6 796 | 797 | ypos 798 | 470 799 | 800 | 801 | webaddress 802 | www.kylen314.com 803 | 804 | 805 | -------------------------------------------------------------------------------- /BilibiliSearch/Source/support.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Mon May 26 23:59:09 2014 4 | 5 | @author: Vespa 6 | """ 7 | import urllib2 8 | import urllib 9 | import re 10 | import json 11 | import zlib 12 | import gzip 13 | import xml.dom.minidom 14 | import hashlib 15 | from biclass import * 16 | import time 17 | import sys 18 | import os 19 | 20 | def GetRE(content,regexp): 21 | return re.findall(regexp, content) 22 | 23 | def getURLContent(url): 24 | while True: 25 | flag = 1 26 | try: 27 | headers = {'User-Agent':'Mozilla/5.0 (Windows U Windows NT 6.1 en-US rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6'} 28 | req = urllib2.Request(url = url,headers = headers) 29 | content = urllib2.urlopen(req).read() 30 | except: 31 | flag = 0 32 | time.sleep(5) 33 | if flag == 1: 34 | break 35 | return content 36 | 37 | class JsonInfo(): 38 | def __init__(self,url): 39 | self.info = json.loads(getURLContent(url)) 40 | if self.info.has_key('code') and self.info['code'] != 0: 41 | if self.info.has_key('message'): 42 | print "【Error】code=%d, msg=%s, url=%s"%(self.info['code'],self.Getvalue('message'),url) 43 | elif self.info.has_key('error'): 44 | print "【Error】code=%d, msg=%s, url=%s"%(self.info['code'],self.Getvalue('error'),url) 45 | error = True 46 | def Getvalue(self,*keys): 47 | if len(keys) == 0: 48 | return None 49 | if self.info.has_key(keys[0]): 50 | temp = self.info[keys[0]] 51 | else: 52 | return None 53 | if len(keys) > 1: 54 | for key in keys[1:]: 55 | if temp.has_key(key): 56 | temp = temp[key] 57 | else: 58 | return None 59 | if isinstance(temp,unicode): 60 | temp = temp.encode('utf8') 61 | return temp 62 | info = None 63 | error = False 64 | 65 | def GetString(t): 66 | if type(t) == int: 67 | return str(t) 68 | return t 69 | 70 | def getint(string): 71 | try: 72 | i = int(string) 73 | except: 74 | i = 0 75 | return i 76 | 77 | def DictDecode2UTF8(dict): 78 | for keys in dict: 79 | if isinstance(dict[keys],unicode): 80 | dict[keys] = dict[keys].encode('utf8') 81 | return dict 82 | 83 | def GetVideoFromRate(content): 84 | """ 85 | 从视频搜索源码页面提取视频信息 86 | """ 87 | #av号和标题 88 | regular1 = r']*>([^/]+)' 89 | info1 = GetRE(content,regular1) 90 | #观看数 91 | regular2 = r']*>(.+)' 92 | info2 = GetRE(content,regular2) 93 | #收藏 94 | regular3 = r']*>(.+)' 95 | info3 = GetRE(content,regular3) 96 | #弹幕 97 | regular4 = r']*>(.+)' 98 | info4 = GetRE(content,regular4) 99 | #日期 100 | regular5 = r'(\d+-\d+-\d+ \d+:\d+)' 101 | info5 = GetRE(content,regular5) 102 | #封面 103 | regular6 = r']*>' 104 | info6 = GetRE(content,regular6) 105 | #Up的id和名字 106 | regular7 = r'(.*)' 107 | info7 = GetRE(content,regular7) 108 | #!!!!!!!!这里可以断言所有信息长度相等 109 | videoNum = len(info1)#视频长度 110 | videoList = [] 111 | for i in range(videoNum): 112 | video_t = Video() 113 | video_t.aid = getint(info1[i][0]) 114 | video_t.title = info1[i][1] 115 | video_t.guankan = getint(info2[i]) 116 | video_t.shoucang = getint(info3[i]) 117 | video_t.danmu = getint(info4[i]) 118 | video_t.date = info5[i] 119 | video_t.cover = info6[i] 120 | video_t.author = User(info7[i][0],info7[i][1]) 121 | videoList.append(video_t) 122 | return videoList 123 | 124 | def GetSign(params, appkey, AppSecret=None): 125 | """ 126 | 获取新版API的签名,不然会返回-3错误 127 | """ 128 | params['appkey']=appkey 129 | data = "" 130 | paras = params.keys() 131 | paras.sort() 132 | data = urllib.urlencode(params) 133 | if AppSecret == None: 134 | return data 135 | m = hashlib.md5() 136 | m.update(data+AppSecret) 137 | return data+'&sign='+m.hexdigest() 138 | 139 | def ParseComment(danmu): 140 | dom = xml.dom.minidom.parseString(danmu) 141 | comment_element = dom.getElementsByTagName('d') 142 | for i, comment in enumerate(comment_element): 143 | p = str(comment.getAttribute('p')).split(',') 144 | danmu = Danmu() 145 | danmu.t_video = float(p[0]) 146 | danmu.danmu_type = int(p[1]) 147 | danmu.t_stamp = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(float(p[4]))) 148 | danmu.mid_crc = p[6] 149 | danmu.danmu_color = ConvertColor(int(p[3])) 150 | if len(comment.childNodes) != 0: 151 | danmu.content = str(comment.childNodes[0].wholeText).replace('/n', '\n') 152 | else: 153 | danmu.content = "" 154 | yield danmu -------------------------------------------------------------------------------- /BilibiliSearch/bilibili.alfredworkflow: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vespa314/BilibiliAlfredWorkFlows/43f3a27d35bd28118bf9935d4236344b2d5d86d7/BilibiliSearch/bilibili.alfredworkflow -------------------------------------------------------------------------------- /Biliurl/Source/Feedback.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Fri Sep 26 00:07:49 2014 4 | 5 | @author: vespa 6 | """ 7 | import sys 8 | 9 | import xml.etree.ElementTree as et 10 | default_encoding = 'utf-8' 11 | if sys.getdefaultencoding() != default_encoding: 12 | reload(sys) 13 | sys.setdefaultencoding(default_encoding) 14 | 15 | class Feedback(): 16 | """Feeback used by Alfred Script Filter 17 | 18 | Usage: 19 | fb = Feedback() 20 | fb.add_item('Hello', 'World') 21 | fb.add_item('Foo', 'Bar') 22 | print fb 23 | 24 | """ 25 | 26 | def __init__(self): 27 | self.feedback = et.Element('items') 28 | 29 | def __repr__(self): 30 | """XML representation used by Alfred 31 | 32 | Returns: 33 | XML string 34 | """ 35 | return et.tostring(self.feedback) 36 | 37 | def add_item(self, title, subtitle = "", arg = "", valid = "yes", autocomplete = "", icon = "icon.png"): 38 | """ 39 | Add item to alfred Feedback 40 | 41 | Args: 42 | title(str): the title displayed by Alfred 43 | Keyword Args: 44 | subtitle(str): the subtitle displayed by Alfred 45 | arg(str): the value returned by alfred when item is selected 46 | valid(str): whether or not the entry can be selected in Alfred to trigger an action 47 | autcomplete(str): the text to be inserted if an invalid item is selected. This is only used if 'valid' is 'no' 48 | icon(str): filename of icon that Alfred will display 49 | """ 50 | item = et.SubElement(self.feedback, 'item', uid=str(len(self.feedback)), arg=arg, valid=valid, autocomplete=autocomplete) 51 | _title = et.SubElement(item, 'title') 52 | _title.text = title 53 | _sub = et.SubElement(item, 'subtitle') 54 | _sub.text = subtitle 55 | _icon = et.SubElement(item, 'icon') 56 | _icon.text = icon -------------------------------------------------------------------------------- /Biliurl/Source/biclass.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Wed May 28 01:22:20 2014 4 | 5 | @author: Vespa 6 | """ 7 | 8 | class User(): 9 | def __init__(self,m_mid=None,m_name=None): 10 | if m_mid: 11 | self.mid = m_mid 12 | if m_name: 13 | if isinstance(m_name,unicode): 14 | m_name = m_name.encode('utf8') 15 | self.name = m_name 16 | # 获取空间地址 17 | def GetSpace(self): 18 | return 'http://space.bilibili.tv/'+str(self.mid) 19 | mid = None 20 | name = None 21 | isApprove = None#是否是认证账号 22 | spaceName = None 23 | sex = None 24 | rank = None 25 | avatar = None 26 | follow = None#关注好友数目 27 | fans = None#粉丝数目 28 | article = None#投稿数 29 | place = None#所在地 30 | description = None#认证用户为认证信息 普通用户为交友宣言 31 | followlist = None#关注的好友列表 32 | friend = None 33 | DisplayRank = None 34 | 35 | 36 | class Video(): 37 | def __init__(self,m_aid=None,m_title=None): 38 | if m_aid: 39 | self.aid = m_aid 40 | if m_title: 41 | if isinstance(m_title,unicode): 42 | m_title = m_title.encode('utf8') 43 | self.title = m_title 44 | aid = None 45 | title = None 46 | guankan = None 47 | shoucang = None 48 | danmu = None 49 | date = None 50 | cover = None 51 | commentNumber = None 52 | description = None 53 | tag = None 54 | author = None 55 | page = None 56 | credit = None 57 | coin = None 58 | spid = None 59 | cid = None 60 | offsite = None#Flash播放调用地址 61 | Iscopy = None 62 | subtitle = None 63 | duration = None 64 | episode = None 65 | arcurl = None#网页地址 66 | arcrank = None#不明 67 | #不明: 68 | tid = None 69 | typename = None 70 | instant_server = None 71 | src = None 72 | partname = None 73 | allow_bp = None 74 | allow_feed = None 75 | created = None 76 | #播放信息: 77 | play_site = None 78 | play_forward = None 79 | play_mobile = None 80 | 81 | class Bangumi(): 82 | def __init__(self): 83 | pass 84 | typeid = None 85 | lastupdate = None 86 | areaid = None 87 | bgmcount = None#番剧当前总集数 88 | title = None 89 | lastupdate_at = None 90 | attention = None #订阅数 91 | cover = None 92 | priority = None 93 | area = None 94 | weekday = None 95 | spid = None 96 | new = None 97 | scover = None 98 | mcover = None 99 | click = None 100 | season_id = None 101 | click = None # 浏览数 102 | video_view = None 103 | 104 | class Comment(): 105 | def __init__(self): 106 | self.post_user = User() 107 | lv = None#楼层 108 | fbid = None#评论id 109 | msg = None 110 | ad_check = None#状态 (0: 正常 1: UP主隐藏 2: 管理员删除 3: 因举报删除) 111 | post_user = None 112 | 113 | class CommentList(): 114 | def __init__(self): 115 | pass 116 | comments = None 117 | commentLen = None 118 | page = None 119 | 120 | class ZhuantiInfo(): 121 | def __init__(self, m_spid,m_title): 122 | self.spid = m_spid 123 | if isinstance(m_title,unicode): 124 | m_title = m_title.encode('utf8') 125 | self.title = m_title 126 | spid = None 127 | title = None 128 | author = None 129 | cover = None 130 | thumb = None 131 | ischeck = None #不明 132 | typeurl = None #总是"http://www.bilibili.com" 133 | tag = None 134 | description = None 135 | pubdate = None # 不明 136 | postdate = None 137 | lastupdate = None 138 | click = None 139 | favourite = None 140 | attention = None 141 | count = None 142 | bgmcount = None 143 | spcount = None 144 | season_id = None 145 | is_bangumi = None 146 | arcurl = None -------------------------------------------------------------------------------- /Biliurl/Source/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vespa314/BilibiliAlfredWorkFlows/43f3a27d35bd28118bf9935d4236344b2d5d86d7/Biliurl/Source/icon.png -------------------------------------------------------------------------------- /Biliurl/Source/main.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Mon May 26 23:42:03 2014 4 | 5 | @author: Vespa 6 | """ 7 | 8 | 9 | from support import * 10 | from Feedback import * 11 | 12 | USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.99 Safari/537.36' 13 | APPKEY = '85eb6835b0a1034e' 14 | APPSEC = '2ad42749773c441109bdc0191257a664' 15 | 16 | default_encoding = 'utf-8' 17 | if sys.getdefaultencoding() != default_encoding: 18 | reload(sys) 19 | sys.setdefaultencoding(default_encoding) 20 | 21 | def GetVideoInfo(aid, appkey,page = 1, AppSecret=None, fav = None): 22 | """ 23 | 获取视频信息 24 | 输入: 25 | aid:AV号 26 | page:页码 27 | fav:是否读取会员收藏状态 (默认 0) 28 | """ 29 | paras = {'id': GetString(aid),'page': GetString(page)} 30 | if fav: 31 | paras['fav'] = fav 32 | url = 'http://api.bilibili.cn/view?'+GetSign(paras,appkey,AppSecret) 33 | jsoninfo = JsonInfo(url) 34 | video = Video(aid,jsoninfo.Getvalue('title')) 35 | video.cid = jsoninfo.Getvalue('cid') 36 | return video 37 | 38 | def GetBilibiliUrl(url): 39 | global video 40 | overseas=False 41 | url_get_media = 'http://interface.bilibili.com/playurl?' if not overseas else 'http://interface.bilibili.com/v_cdn_play?' 42 | regex_match = re.findall('http:/*[^/]+/video/av(\\d+)(/|/index.html|/index_(\\d+).html)?(\\?|#|$)',url) 43 | if not regex_match: 44 | raise ValueError('Invalid URL: %s' % url) 45 | aid = regex_match[0][0] 46 | pid = regex_match[0][2] or '1' 47 | cid_args = {'type': 'json', 'id': aid, 'page': pid} 48 | 49 | cid = video.cid 50 | media_args = {'otype': 'json', 'cid': cid, 'type': 'mp4', 'quality': 4, 'appkey': APPKEY} 51 | resp_media = getURLContent(url_get_media+GetSign(media_args,APPKEY,APPSEC)) 52 | resp_media = dict(json.loads(resp_media.decode('utf-8', 'replace'))) 53 | media_urls = resp_media.get('durl') 54 | res = [] 55 | for media_url in media_urls: 56 | res.append(media_url.get('url')) 57 | return res 58 | 59 | def GetSign(params,appkey,AppSecret=None): 60 | params['appkey']=appkey; 61 | data = ""; 62 | paras = sorted(params) 63 | paras.sort(); 64 | for para in paras: 65 | if data != "": 66 | data += "&"; 67 | data += para + "=" + str(params[para]); 68 | if AppSecret == None: 69 | return data 70 | m = hashlib.md5() 71 | m.update((data+AppSecret).encode('utf-8')) 72 | return data+'&sign='+m.hexdigest() 73 | 74 | def ChangeFuck(params): 75 | data = ""; 76 | paras = params; 77 | for para in paras: 78 | if data != "": 79 | data += "&"; 80 | data += para + "=" + str(params[para]); 81 | return data 82 | 83 | fb = Feedback() 84 | url = '{query}' 85 | # url = 'http://www.bilibili.com/video/av2968792/' 86 | av = GetRE(url,r"\d+")[0] 87 | video = GetVideoInfo(av,appkey=APPKEY,AppSecret=APPSEC) 88 | downloadUrls = GetBilibiliUrl(url) 89 | # print video.title 90 | # for downloadUrl in downloadUrls: 91 | # print downloadUrl 92 | 93 | for id,downloadUrl in enumerate(downloadUrls): 94 | fb.add_item('获取'+url+'下载地址',subtitle="%s(%d P)"%(video.title,id+1),arg=downloadUrl) 95 | 96 | print fb -------------------------------------------------------------------------------- /Biliurl/Source/support.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Mon May 26 23:59:09 2014 4 | 5 | @author: Vespa 6 | """ 7 | import urllib2 8 | import urllib 9 | import re 10 | import json 11 | import zlib 12 | import gzip 13 | import xml.dom.minidom 14 | import hashlib 15 | from biclass import * 16 | import time 17 | import sys 18 | import os 19 | from GetAssDanmaku import * 20 | def GetRE(content,regexp): 21 | return re.findall(regexp, content) 22 | 23 | def getURLContent(url): 24 | while True: 25 | flag = 1 26 | try: 27 | headers = {'User-Agent':'Mozilla/5.0 (Windows U Windows NT 6.1 en-US rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6'} 28 | req = urllib2.Request(url = url,headers = headers) 29 | content = urllib2.urlopen(req).read() 30 | except: 31 | flag = 0 32 | time.sleep(5) 33 | if flag == 1: 34 | break 35 | return content 36 | 37 | class JsonInfo(): 38 | def __init__(self,url): 39 | self.info = json.loads(getURLContent(url)) 40 | if self.info.has_key('code') and self.info['code'] != 0: 41 | if self.info.has_key('message'): 42 | print "【Error】code=%d, msg=%s, url=%s"%(self.info['code'],self.Getvalue('message'),url) 43 | elif self.info.has_key('error'): 44 | print "【Error】code=%d, msg=%s, url=%s"%(self.info['code'],self.Getvalue('error'),url) 45 | error = True 46 | def Getvalue(self,*keys): 47 | if len(keys) == 0: 48 | return None 49 | if self.info.has_key(keys[0]): 50 | temp = self.info[keys[0]] 51 | else: 52 | return None 53 | if len(keys) > 1: 54 | for key in keys[1:]: 55 | if temp.has_key(key): 56 | temp = temp[key] 57 | else: 58 | return None 59 | if isinstance(temp,unicode): 60 | temp = temp.encode('utf8') 61 | return temp 62 | info = None 63 | error = False 64 | 65 | def GetString(t): 66 | if type(t) == int: 67 | return str(t) 68 | return t 69 | 70 | def getint(string): 71 | try: 72 | i = int(string) 73 | except: 74 | i = 0 75 | return i 76 | 77 | def DictDecode2UTF8(dict): 78 | for keys in dict: 79 | if isinstance(dict[keys],unicode): 80 | dict[keys] = dict[keys].encode('utf8') 81 | return dict 82 | 83 | def GetVideoFromRate(content): 84 | """ 85 | 从视频搜索源码页面提取视频信息 86 | """ 87 | #av号和标题 88 | regular1 = r']*>([^/]+)' 89 | info1 = GetRE(content,regular1) 90 | #观看数 91 | regular2 = r']*>(.+)' 92 | info2 = GetRE(content,regular2) 93 | #收藏 94 | regular3 = r']*>(.+)' 95 | info3 = GetRE(content,regular3) 96 | #弹幕 97 | regular4 = r']*>(.+)' 98 | info4 = GetRE(content,regular4) 99 | #日期 100 | regular5 = r'(\d+-\d+-\d+ \d+:\d+)' 101 | info5 = GetRE(content,regular5) 102 | #封面 103 | regular6 = r']*>' 104 | info6 = GetRE(content,regular6) 105 | #Up的id和名字 106 | regular7 = r'(.*)' 107 | info7 = GetRE(content,regular7) 108 | #!!!!!!!!这里可以断言所有信息长度相等 109 | videoNum = len(info1)#视频长度 110 | videoList = [] 111 | for i in range(videoNum): 112 | video_t = Video() 113 | video_t.aid = getint(info1[i][0]) 114 | video_t.title = info1[i][1] 115 | video_t.guankan = getint(info2[i]) 116 | video_t.shoucang = getint(info3[i]) 117 | video_t.danmu = getint(info4[i]) 118 | video_t.date = info5[i] 119 | video_t.cover = info6[i] 120 | video_t.author = User(info7[i][0],info7[i][1]) 121 | videoList.append(video_t) 122 | return videoList 123 | 124 | def GetSign(params, appkey, AppSecret=None): 125 | """ 126 | 获取新版API的签名,不然会返回-3错误 127 | """ 128 | params['appkey']=appkey 129 | data = "" 130 | paras = params.keys() 131 | paras.sort() 132 | data = urllib.urlencode(params) 133 | if AppSecret == None: 134 | return data 135 | m = hashlib.md5() 136 | m.update(data+AppSecret) 137 | return data+'&sign='+m.hexdigest() 138 | 139 | def ParseComment(danmu): 140 | dom = xml.dom.minidom.parseString(danmu) 141 | comment_element = dom.getElementsByTagName('d') 142 | for i, comment in enumerate(comment_element): 143 | p = str(comment.getAttribute('p')).split(',') 144 | if len(comment.childNodes) != 0: 145 | c = str(comment.childNodes[0].wholeText).replace('/n', '\n') 146 | else: 147 | c = "" 148 | yield (float(p[0]),c) -------------------------------------------------------------------------------- /Biliurl/biliurl.alfredworkflow: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vespa314/BilibiliAlfredWorkFlows/43f3a27d35bd28118bf9935d4236344b2d5d86d7/Biliurl/biliurl.alfredworkflow -------------------------------------------------------------------------------- /GetAssFromBilibili/danmu.alfredworkflow: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vespa314/BilibiliAlfredWorkFlows/43f3a27d35bd28118bf9935d4236344b2d5d86d7/GetAssFromBilibili/danmu.alfredworkflow -------------------------------------------------------------------------------- /GetAssFromBilibili/source/Feedback.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Fri Sep 26 00:07:49 2014 4 | 5 | @author: vespa 6 | """ 7 | import sys 8 | 9 | import xml.etree.ElementTree as et 10 | default_encoding = 'utf-8' 11 | if sys.getdefaultencoding() != default_encoding: 12 | reload(sys) 13 | sys.setdefaultencoding(default_encoding) 14 | 15 | class Feedback(): 16 | """Feeback used by Alfred Script Filter 17 | 18 | Usage: 19 | fb = Feedback() 20 | fb.add_item('Hello', 'World') 21 | fb.add_item('Foo', 'Bar') 22 | print fb 23 | 24 | """ 25 | 26 | def __init__(self): 27 | self.feedback = et.Element('items') 28 | 29 | def __repr__(self): 30 | """XML representation used by Alfred 31 | 32 | Returns: 33 | XML string 34 | """ 35 | return et.tostring(self.feedback) 36 | 37 | def add_item(self, title, subtitle = "", arg = "", valid = "yes", autocomplete = "", icon = "icon.png"): 38 | """ 39 | Add item to alfred Feedback 40 | 41 | Args: 42 | title(str): the title displayed by Alfred 43 | Keyword Args: 44 | subtitle(str): the subtitle displayed by Alfred 45 | arg(str): the value returned by alfred when item is selected 46 | valid(str): whether or not the entry can be selected in Alfred to trigger an action 47 | autcomplete(str): the text to be inserted if an invalid item is selected. This is only used if 'valid' is 'no' 48 | icon(str): filename of icon that Alfred will display 49 | """ 50 | item = et.SubElement(self.feedback, 'item', uid=str(len(self.feedback)), arg=arg, valid=valid, autocomplete=autocomplete) 51 | _title = et.SubElement(item, 'title') 52 | _title.text = title 53 | _sub = et.SubElement(item, 'subtitle') 54 | _sub.text = subtitle 55 | _icon = et.SubElement(item, 'icon') 56 | _icon.text = icon -------------------------------------------------------------------------------- /GetAssFromBilibili/source/biclass.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Wed May 28 01:22:20 2014 4 | 5 | @author: Administrator 6 | """ 7 | 8 | class User(): 9 | def __init__(self,m_mid=None,m_name=None): 10 | if m_mid: 11 | self.mid = m_mid; 12 | if m_name: 13 | self.name = m_name; 14 | def saveToFile(self,fid): 15 | fid.write('名字:%s\n'%self.name); 16 | fid.write('id:%s\n'%self.mid); 17 | fid.write('是否认证:%s\n'%self.isApprove); 18 | fid.write('空间:%s\n'%self.spaceName); 19 | fid.write('性别:%s\n'%self.sex); 20 | fid.write('账号显示标识:%s\n'%self.rank); 21 | fid.write('头像:%s\n'%self.avatar); 22 | fid.write('关注好友数目:%d\n'%self.follow); 23 | fid.write('粉丝数目:%d\n'%self.fans); 24 | fid.write('投稿数:%d\n'%self.article); 25 | fid.write('地点:%s\n'%self.place); 26 | fid.write('认证信息:%s\n'%self.description); 27 | fid.write('关注好友:\n'); 28 | if self.followlist: 29 | for fo in self.followlist: 30 | fid.write('\t%s\n'%fo); 31 | # 获取空间地址 32 | def GetSpace(self): 33 | return 'http://space.bilibili.tv/'+str(self.mid); 34 | mid = None; 35 | name = None; 36 | isApprove = False;#是否是认证账号 37 | spaceName = ""; 38 | sex = "" 39 | rank = None; 40 | avatar = None; 41 | follow = 0;#关注好友数目 42 | fans = 0;#粉丝数目 43 | article = 0;#投稿数 44 | place = None;#所在地 45 | description = None;#认证用户为认证信息 普通用户为交友宣言 46 | followlist = None;#关注的好友列表 47 | 48 | 49 | class Vedio(): 50 | def __init__(self,m_aid=None,m_title=None): 51 | if m_aid: 52 | self.aid = m_aid; 53 | if m_title: 54 | self.title = m_title; 55 | # 写到文件中 56 | def saveToFile(self,fid): 57 | fid.write('av号:%d\n'%self.aid); 58 | fid.write('标题:%s\n'%self.title); 59 | fid.write('观看:%d\n'%self.guankan); 60 | fid.write('收藏:%d\n'%self.shoucang); 61 | fid.write('弹幕:%d\n'%self.danmu); 62 | fid.write('日期:%s\n'%self.date); 63 | fid.write('封面地址:%s\n'%self.cover); 64 | fid.write('Up主:\n'); 65 | self.author.saveToFile(fid); 66 | fid.write('\n'); 67 | aid = None; 68 | title = None; 69 | guankan = None; 70 | shoucang = None; 71 | danmu = None; 72 | date = None; 73 | cover = None; 74 | commentNumber = None; 75 | description = None; 76 | tag = None; 77 | author = None; 78 | page = None; 79 | credit = None; 80 | coin = None; 81 | spid = None; 82 | cid = None; 83 | offsite = None;#Flash播放调用地址 84 | Iscopy = None; 85 | subtitle = None; 86 | duration = None; 87 | episode = None; 88 | #不明: 89 | tid = None; 90 | typename = None; 91 | instant_server = None; 92 | src = None; 93 | partname = None; 94 | #播放信息: 95 | play_site = None; 96 | play_forward = None; 97 | play_mobile = None; 98 | 99 | class Bangumi(): 100 | def __init__(self): 101 | pass; 102 | typeid = None; 103 | lastupdate = None; 104 | areaid = None; 105 | bgmcount = None;#番剧当前总集数 106 | title = None; 107 | lastupdate_at = None; 108 | attention = None; 109 | cover = None; 110 | priority = None; 111 | area = None; 112 | weekday = None; 113 | spid = None; 114 | new = None; 115 | scover = None; 116 | mcover = None; 117 | click = None; 118 | season_id = None; 119 | 120 | class Comment(): 121 | def __init__(self): 122 | self.post_user = User(); 123 | lv = None;#楼层 124 | fbid = None;#评论id 125 | msg = None; 126 | ad_check = None;#状态 (0: 正常 1: UP主隐藏 2: 管理员删除 3: 因举报删除) 127 | post_user = None; 128 | 129 | class CommentList(): 130 | def __init__(self): 131 | pass; 132 | comments = None; 133 | commentLen = None; 134 | page = None; 135 | 136 | -------------------------------------------------------------------------------- /GetAssFromBilibili/source/getAss.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Mon May 26 23:42:03 2014 4 | 5 | @author: Administrator 6 | """ 7 | 8 | 9 | from support import * 10 | import hashlib 11 | import io 12 | import xml.dom.minidom 13 | import random 14 | import math 15 | import os 16 | import sys 17 | from Feedback import * 18 | 19 | default_encoding = 'utf-8' 20 | if sys.getdefaultencoding() != default_encoding: 21 | reload(sys) 22 | sys.setdefaultencoding(default_encoding) 23 | 24 | class safe_list(list): 25 | def get(self, index, default=None): 26 | try: 27 | return self[index] 28 | except IndexError: 29 | return default 30 | 31 | # Calculation is based on https://github.com/jabbany/CommentCoreLibrary/issues/5#issuecomment-40087282 32 | # and https://github.com/m13253/danmaku2ass/issues/7#issuecomment-41489422 33 | # ASS FOV = width*4/3.0 34 | # But Flash FOV = width/math.tan(100*math.pi/360.0)/2 will be used instead 35 | # Result: (transX, transY, rotX, rotY, rotZ, scaleX, scaleY) 36 | def ConvertFlashRotation(rotY, rotZ, X, Y, width, height): 37 | def WrapAngle(deg): 38 | return 180-((180-deg) % 360) 39 | rotY = WrapAngle(rotY) 40 | rotZ = WrapAngle(rotZ) 41 | if rotY in (90, -90): 42 | rotY -= 1 43 | if rotY == 0 or rotZ == 0: 44 | outX = 0 45 | outY = -rotY # Positive value means clockwise in Flash 46 | outZ = -rotZ 47 | rotY *= math.pi/180.0 48 | rotZ *= math.pi/180.0 49 | else: 50 | rotY *= math.pi/180.0 51 | rotZ *= math.pi/180.0 52 | outY = math.atan2(-math.sin(rotY)*math.cos(rotZ), math.cos(rotY))*180/math.pi 53 | outZ = math.atan2(-math.cos(rotY)*math.sin(rotZ), math.cos(rotZ))*180/math.pi 54 | outX = math.asin(math.sin(rotY)*math.sin(rotZ))*180/math.pi 55 | trX = (X*math.cos(rotZ)+Y*math.sin(rotZ))/math.cos(rotY)+(1-math.cos(rotZ)/math.cos(rotY))*width/2-math.sin(rotZ)/math.cos(rotY)*height/2 56 | trY = Y*math.cos(rotZ)-X*math.sin(rotZ)+math.sin(rotZ)*width/2+(1-math.cos(rotZ))*height/2 57 | trZ = (trX-width/2)*math.sin(rotY) 58 | FOV = width*math.tan(2*math.pi/9.0)/2 59 | try: 60 | scaleXY = FOV/(FOV+trZ) 61 | except ZeroDivisionError: 62 | logging.error('Rotation makes object behind the camera: trZ == %.0f' % trZ) 63 | scaleXY = 1 64 | trX = (trX-width/2)*scaleXY+width/2 65 | trY = (trY-height/2)*scaleXY+height/2 66 | if scaleXY < 0: 67 | scaleXY = -scaleXY 68 | outX += 180 69 | outY += 180 70 | logging.error('Rotation makes object behind the camera: trZ == %.0f < %.0f' % (trZ, FOV)) 71 | return (trX, trY, WrapAngle(outX), WrapAngle(outY), WrapAngle(outZ), scaleXY*100, scaleXY*100) 72 | 73 | 74 | def WriteCommentBilibiliPositioned(f, c, width, height, styleid): 75 | #BiliPlayerSize = (512, 384) # Bilibili player version 2010 76 | #BiliPlayerSize = (540, 384) # Bilibili player version 2012 77 | BiliPlayerSize = (672, 438) # Bilibili player version 2014 78 | ZoomFactor = GetZoomFactor(BiliPlayerSize, (width, height)) 79 | 80 | def GetPosition(InputPos, isHeight): 81 | isHeight = int(isHeight) # True -> 1 82 | if isinstance(InputPos, int): 83 | return ZoomFactor[0]*InputPos+ZoomFactor[isHeight+1] 84 | elif isinstance(InputPos, float): 85 | if InputPos > 1: 86 | return ZoomFactor[0]*InputPos+ZoomFactor[isHeight+1] 87 | else: 88 | return BiliPlayerSize[isHeight]*ZoomFactor[0]*InputPos+ZoomFactor[isHeight+1] 89 | else: 90 | try: 91 | InputPos = int(InputPos) 92 | except ValueError: 93 | InputPos = float(InputPos) 94 | return GetPosition(InputPos, isHeight) 95 | 96 | try: 97 | comment_args = safe_list(json.loads(c[3])) 98 | text = ASSEscape(str(comment_args[4]).replace('/n', '\n')) 99 | from_x = comment_args.get(0, 0) 100 | from_y = comment_args.get(1, 0) 101 | to_x = comment_args.get(7, from_x) 102 | to_y = comment_args.get(8, from_y) 103 | from_x = GetPosition(from_x, False) 104 | from_y = GetPosition(from_y, True) 105 | to_x = GetPosition(to_x, False) 106 | to_y = GetPosition(to_y, True) 107 | alpha = safe_list(str(comment_args.get(2, '1')).split('-')) 108 | from_alpha = float(alpha.get(0, 1)) 109 | to_alpha = float(alpha.get(1, from_alpha)) 110 | from_alpha = 255-round(from_alpha*255) 111 | to_alpha = 255-round(to_alpha*255) 112 | rotate_z = int(comment_args.get(5, 0)) 113 | rotate_y = int(comment_args.get(6, 0)) 114 | lifetime = float(comment_args.get(3, 4500)) 115 | duration = int(comment_args.get(9, lifetime*1000)) 116 | delay = int(comment_args.get(10, 0)) 117 | fontface = comment_args.get(12) 118 | isborder = comment_args.get(11, 'true') 119 | from_rotarg = ConvertFlashRotation(rotate_y, rotate_z, from_x, from_y, width, height) 120 | to_rotarg = ConvertFlashRotation(rotate_y, rotate_z, to_x, to_y, width, height) 121 | styles = ['\\org(%d, %d)' % (width/2, height/2)] 122 | if from_rotarg[0:2] == to_rotarg[0:2]: 123 | styles.append('\\pos(%.0f, %.0f)' % (from_rotarg[0:2])) 124 | else: 125 | styles.append('\\move(%.0f, %.0f, %.0f, %.0f, %.0f, %.0f)' % (from_rotarg[0:2]+to_rotarg[0:2]+(delay, delay+duration))) 126 | styles.append('\\frx%.0f\\fry%.0f\\frz%.0f\\fscx%.0f\\fscy%.0f' % (from_rotarg[2:7])) 127 | if (from_x, from_y) != (to_x, to_y): 128 | styles.append('\\t(%d, %d, ' % (delay, delay+duration)) 129 | styles.append('\\frx%.0f\\fry%.0f\\frz%.0f\\fscx%.0f\\fscy%.0f' % (to_rotarg[2:7])) 130 | styles.append(')') 131 | if fontface: 132 | styles.append('\\fn%s' % ASSEscape(fontface)) 133 | styles.append('\\fs%.0f' % (c[6]*ZoomFactor[0])) 134 | if c[5] != 0xffffff: 135 | styles.append('\\c&H%s&' % ConvertColor(c[5])) 136 | if c[5] == 0x000000: 137 | styles.append('\\3c&HFFFFFF&') 138 | if from_alpha == to_alpha: 139 | styles.append('\\alpha&H%02X' % from_alpha) 140 | elif (from_alpha, to_alpha) == (255, 0): 141 | styles.append('\\fad(%.0f,0)' % (lifetime*1000)) 142 | elif (from_alpha, to_alpha) == (0, 255): 143 | styles.append('\\fad(0, %.0f)' % (lifetime*1000)) 144 | else: 145 | styles.append('\\fade(%(from_alpha)d, %(to_alpha)d, %(to_alpha)d, 0, %(end_time).0f, %(end_time).0f, %(end_time).0f)' % {'from_alpha': from_alpha, 'to_alpha': to_alpha, 'end_time': lifetime*1000}) 146 | if isborder == 'false': 147 | styles.append('\\bord0') 148 | f.write('Dialogue: -1,%(start)s,%(end)s,%(styleid)s,,0,0,0,,{%(styles)s}%(text)s\n' % {'start': ConvertTimestamp(c[0]), 'end': ConvertTimestamp(c[0]+lifetime), 'styles': ''.join(styles), 'text': text, 'styleid': styleid}) 149 | except (IndexError, ValueError) as e: 150 | try: 151 | logging.warning(_('Invalid comment: %r') % c[3]) 152 | except IndexError: 153 | logging.warning(_('Invalid comment: %r') % c) 154 | 155 | # Result: (f, dx, dy) 156 | # To convert: NewX = f*x+dx, NewY = f*y+dy 157 | def GetZoomFactor(SourceSize, TargetSize): 158 | try: 159 | if (SourceSize, TargetSize) == GetZoomFactor.Cached_Size: 160 | return GetZoomFactor.Cached_Result 161 | except AttributeError: 162 | pass 163 | GetZoomFactor.Cached_Size = (SourceSize, TargetSize) 164 | try: 165 | SourceAspect = SourceSize[0]/SourceSize[1] 166 | TargetAspect = TargetSize[0]/TargetSize[1] 167 | if TargetAspect < SourceAspect: # narrower 168 | ScaleFactor = TargetSize[0]/SourceSize[0] 169 | GetZoomFactor.Cached_Result = (ScaleFactor, 0, (TargetSize[1]-TargetSize[0]/SourceAspect)/2) 170 | elif TargetAspect > SourceAspect: # wider 171 | ScaleFactor = TargetSize[1]/SourceSize[1] 172 | GetZoomFactor.Cached_Result = (ScaleFactor, (TargetSize[0]-TargetSize[1]*SourceAspect)/2, 0) 173 | else: 174 | GetZoomFactor.Cached_Result = (TargetSize[0]/SourceSize[0], 0, 0) 175 | return GetZoomFactor.Cached_Result 176 | except ZeroDivisionError: 177 | GetZoomFactor.Cached_Result = (1, 0, 0) 178 | return GetZoomFactor.Cached_Result 179 | 180 | 181 | def WriteASSHead(f, width, height, fontface, fontsize, alpha, styleid): 182 | f.write( 183 | ''' 184 | [Script Info] 185 | ; Script generated by Danmaku2ASS 186 | ; https://github.com/m13253/danmaku2ass 187 | Script Updated By: Danmaku2ASS (https://github.com/m13253/danmaku2ass) 188 | ScriptType: v4.00+ 189 | PlayResX: %(width)d 190 | PlayResY: %(height)d 191 | Aspect Ratio: %(width)d:%(height)d 192 | Collisions: Normal 193 | WrapStyle: 2 194 | ScaledBorderAndShadow: yes 195 | YCbCr Matrix: TV.601 196 | 197 | [V4+ Styles] 198 | Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding 199 | Style: %(styleid)s, %(fontface)s, %(fontsize).0f, &H%(alpha)02XFFFFFF, &H%(alpha)02XFFFFFF, &H%(alpha)02X000000, &H%(alpha)02X000000, 0, 0, 0, 0, 100, 100, 0.00, 0.00, 1, %(outline).0f, 0, 7, 0, 0, 0, 0 200 | 201 | [Events] 202 | Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text 203 | ''' % {'width': width, 'height': height, 'fontface': fontface, 'fontsize': fontsize, 'alpha': 255-round(alpha*255), 'outline': max(fontsize/25.0, 1), 'styleid': styleid} 204 | ) 205 | 206 | def TestFreeRows(rows, c, row, width, height, bottomReserved, lifetime): 207 | res = 0 208 | rowmax = height-bottomReserved 209 | targetRow = None 210 | if c[4] in (1, 2): 211 | while row < rowmax and res < c[7]: 212 | if targetRow != rows[c[4]][row]: 213 | targetRow = rows[c[4]][row] 214 | if targetRow and targetRow[0]+lifetime > c[0]: 215 | break 216 | row += 1 217 | res += 1 218 | else: 219 | try: 220 | thresholdTime = c[0]-lifetime*(1-width/(c[8]+width)) 221 | except ZeroDivisionError: 222 | thresholdTime = c[0]-lifetime 223 | while row < rowmax and res < c[7]: 224 | if targetRow != rows[c[4]][row]: 225 | targetRow = rows[c[4]][row] 226 | try: 227 | if targetRow and (targetRow[0] > thresholdTime or targetRow[0]+targetRow[8]*lifetime/(targetRow[8]+width) > c[0]): 228 | break 229 | except ZeroDivisionError: 230 | pass 231 | row += 1 232 | res += 1 233 | return res 234 | 235 | def MarkCommentRow(rows, c, row): 236 | row = int(row) 237 | try: 238 | for i in range(row, int(row+math.ceil(c[7]))): 239 | rows[c[4]][i] = c 240 | except IndexError: 241 | pass 242 | 243 | def ASSEscape(s): 244 | def ReplaceLeadingSpace(s): 245 | sstrip = s.strip(' ') 246 | slen = len(s) 247 | if slen == len(sstrip): 248 | return s 249 | else: 250 | llen = slen-len(s.lstrip(' ')) 251 | rlen = slen-len(s.rstrip(' ')) 252 | return ''.join(('\u2007'*llen, sstrip, '\u2007'*rlen)) 253 | return '\\N'.join((ReplaceLeadingSpace(i) or ' ' for i in str(s).replace('\\', '\\\\').replace('{', '\\{').replace('}', '\\}').split('\n'))) 254 | 255 | def ConvertTimestamp(timestamp): 256 | timestamp = round(timestamp*100.0) 257 | hour, minute = divmod(timestamp, 360000) 258 | minute, second = divmod(minute, 6000) 259 | second, centsecond = divmod(second, 100) 260 | return '%d:%02d:%02d.%02d' % (int(hour), int(minute), int(second), int(centsecond)) 261 | 262 | def ConvertType2(row, height, bottomReserved): 263 | return height-bottomReserved-row 264 | 265 | def FindAlternativeRow(rows, c, height, bottomReserved): 266 | res = 0 267 | for row in range(int(height-bottomReserved-math.ceil(c[7]))): 268 | if not rows[c[4]][row]: 269 | return row 270 | elif rows[c[4]][row][0] < rows[c[4]][res][0]: 271 | res = row 272 | return res 273 | 274 | def ConvertColor(RGB, width=1280, height=576): 275 | if RGB == 0x000000: 276 | return '000000' 277 | elif RGB == 0xffffff: 278 | return 'FFFFFF' 279 | R = (RGB >> 16) & 0xff 280 | G = (RGB >> 8) & 0xff 281 | B = RGB & 0xff 282 | if width < 1280 and height < 576: 283 | return '%02X%02X%02X' % (B, G, R) 284 | else: # VobSub always uses BT.601 colorspace, convert to BT.709 285 | ClipByte = lambda x: 255 if x > 255 else 0 if x < 0 else round(x) 286 | return '%02X%02X%02X' % ( 287 | ClipByte(R*0.00956384088080656+G*0.03217254540203729+B*0.95826361371715607), 288 | ClipByte(R*-0.10493933142075390+G*1.17231478191855154+B*-0.06737545049779757), 289 | ClipByte(R*0.91348912373987645+G*0.07858536372532510+B*0.00792551253479842) 290 | ) 291 | 292 | def WriteComment(f, c, row, width, height, bottomReserved, fontsize, lifetime, styleid): 293 | text = ASSEscape(c[3]) 294 | styles = [] 295 | if c[4] == 1: 296 | styles.append('\\an8\\pos(%(halfwidth)d, %(row)d)' % {'halfwidth': width/2, 'row': row}) 297 | elif c[4] == 2: 298 | styles.append('\\an2\\pos(%(halfwidth)d, %(row)d)' % {'halfwidth': width/2, 'row': ConvertType2(row, height, bottomReserved)}) 299 | elif c[4] == 3: 300 | styles.append('\\move(%(neglen)d, %(row)d, %(width)d, %(row)d)' % {'width': width, 'row': row, 'neglen': -math.ceil(c[8])}) 301 | else: 302 | styles.append('\\move(%(width)d, %(row)d, %(neglen)d, %(row)d)' % {'width': width, 'row': row, 'neglen': -math.ceil(c[8])}) 303 | if not (-1 < c[6]-fontsize < 1): 304 | styles.append('\\fs%.0f' % c[6]) 305 | if c[5] != 0xffffff: 306 | styles.append('\\c&H%s&' % ConvertColor(c[5])) 307 | if c[5] == 0x000000: 308 | styles.append('\\3c&HFFFFFF&') 309 | ## 替换空格 310 | text = text.replace('\u2007',' ') 311 | f.write('Dialogue: 2,%(start)s,%(end)s,%(styleid)s,,0000,0000,0000,,{%(styles)s}%(text)s\n' % {'start': ConvertTimestamp(c[0]), 'end': ConvertTimestamp(c[0]+lifetime), 'styles': ''.join(styles), 'text': text, 'styleid': styleid}) 312 | 313 | 314 | def CalculateLength(s): 315 | return max(map(len, s.split('\n'))) # May not be accurate 316 | 317 | def GetVedioInfo(aid,appkey,page = 1,AppSecret=None,fav = None): 318 | paras = {'id': GetString(aid),'page': GetString(page)}; 319 | if fav != None: 320 | paras['fav'] = fav; 321 | url = 'http://api.bilibili.cn/view?'+GetSign(paras,appkey,AppSecret); 322 | jsoninfo = JsonInfo(url); 323 | vedio = Vedio(aid,jsoninfo.Getvalue('title')); 324 | vedio.guankan = jsoninfo.Getvalue('play') 325 | vedio.commentNumber = jsoninfo.Getvalue('review') 326 | vedio.danmu = jsoninfo.Getvalue('video_review') 327 | vedio.shoucang = jsoninfo.Getvalue('favorites'); 328 | vedio.description = jsoninfo.Getvalue('description') 329 | vedio.tag = []; 330 | taglist = jsoninfo.Getvalue('tag'); 331 | if taglist != None: 332 | for tag in taglist.split(','): 333 | vedio.tag.append(tag); 334 | vedio.cover = jsoninfo.Getvalue('pic'); 335 | vedio.author = User(jsoninfo.Getvalue('mid'),jsoninfo.Getvalue('author')); 336 | vedio.page = jsoninfo.Getvalue('pages'); 337 | vedio.date = jsoninfo.Getvalue('created_at'); 338 | vedio.credit = jsoninfo.Getvalue('credit'); 339 | vedio.coin = jsoninfo.Getvalue('coins'); 340 | vedio.spid = jsoninfo.Getvalue('spid'); 341 | vedio.cid = jsoninfo.Getvalue('cid'); 342 | vedio.offsite = jsoninfo.Getvalue('offsite'); 343 | vedio.partname = jsoninfo.Getvalue('partname'); 344 | vedio.src = jsoninfo.Getvalue('src'); 345 | vedio.tid = jsoninfo.Getvalue('tid') 346 | vedio.typename = jsoninfo.Getvalue('typename') 347 | vedio.instant_server = jsoninfo.Getvalue('instant_server'); 348 | return vedio 349 | 350 | def GetSign(params,appkey,AppSecret=None): 351 | """ 352 | 获取新版API的签名,不然会返回-3错误 353 | 待添加:【重要!】 354 | 需要做URL编码并保证字母都是大写,如 %2F 355 | """ 356 | params['appkey']=appkey; 357 | data = ""; 358 | paras = params.keys(); 359 | paras.sort(); 360 | for para in paras: 361 | if data != "": 362 | data += "&"; 363 | data += para + "=" + params[para]; 364 | if AppSecret == None: 365 | return data 366 | m = hashlib.md5() 367 | m.update(data+AppSecret) 368 | return data+'&sign='+m.hexdigest() 369 | 370 | 371 | 372 | 373 | def GetDanmuku(cid): 374 | cid = getint(cid); 375 | url = "http://comment.bilibili.cn/%d.xml"%(cid); 376 | content = zlib.decompressobj(-zlib.MAX_WBITS).decompress(getURLContent(url)) 377 | # content = GetRE(content,r']*>([^<]*)<') 378 | return content; 379 | 380 | 381 | #def FilterBadChars(f): 382 | # s = f.read() 383 | # s = re.sub('[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f]', '\ufffd', s) 384 | # return io.StringIO(s) 385 | 386 | def ReadCommentsBilibili(f, fontsize): 387 | dom = xml.dom.minidom.parseString(f) 388 | comment_element = dom.getElementsByTagName('d') 389 | for i, comment in enumerate(comment_element): 390 | try: 391 | p = str(comment.getAttribute('p')).split(',') 392 | assert len(p) >= 5 393 | assert p[1] in ('1', '4', '5', '6', '7') 394 | if p[1] != '7': 395 | c = str(comment.childNodes[0].wholeText).replace('/n', '\n') 396 | size = int(p[2])*fontsize/25.0 397 | yield (float(p[0]), int(p[4]), i, c, {'1': 0, '4': 2, '5': 1, '6': 3}[p[1]], int(p[3]), size, (c.count('\n')+1)*size, CalculateLength(c)*size) 398 | else: # positioned comment 399 | c = str(comment.childNodes[0].wholeText) 400 | yield (float(p[0]), int(p[4]), i, c, 'bilipos', int(p[3]), int(p[2]), 0, 0) 401 | except (AssertionError, AttributeError, IndexError, TypeError, ValueError): 402 | continue 403 | 404 | def ConvertToFile(filename_or_file, *args, **kwargs): 405 | return open(filename_or_file, *args, **kwargs) 406 | 407 | 408 | def ProcessComments(comments, f, width, height, bottomReserved, fontface, fontsize, alpha, lifetime, reduced, progress_callback): 409 | styleid = 'Danmaku2ASS_%04x' % random.randint(0, 0xffff) 410 | WriteASSHead(f, width, height, fontface, fontsize, alpha, styleid) 411 | rows = [[None]*(height-bottomReserved+1) for i in range(4)] 412 | for idx, i in enumerate(comments): 413 | if progress_callback and idx % 1000 == 0: 414 | progress_callback(idx, len(comments)) 415 | if isinstance(i[4], int): 416 | row = 0 417 | rowmax = height-bottomReserved-i[7] 418 | while row <= rowmax: 419 | freerows = TestFreeRows(rows, i, row, width, height, bottomReserved, lifetime) 420 | if freerows >= i[7]: 421 | MarkCommentRow(rows, i, row) 422 | WriteComment(f, i, row, width, height, bottomReserved, fontsize, lifetime, styleid) 423 | break 424 | else: 425 | row += freerows or 1 426 | else: 427 | if not reduced: 428 | row = FindAlternativeRow(rows, i, height, bottomReserved) 429 | MarkCommentRow(rows, i, row) 430 | WriteComment(f, i, row, width, height, bottomReserved, fontsize, lifetime, styleid) 431 | elif i[4] == 'bilipos': 432 | WriteCommentBilibiliPositioned(f, i, width, height, styleid) 433 | elif i[4] == 'acfunpos': 434 | WriteCommentAcfunPositioned(f, i, width, height, styleid) 435 | elif i[4] == 'sH5Vpos': 436 | WriteCommentSH5VPositioned(f, i, width, height, styleid) 437 | else: 438 | logging.warning(_('Invalid comment: %r') % i[3]) 439 | if progress_callback: 440 | progress_callback(len(comments), len(comments)) 441 | 442 | def Danmaku2ASS(input_files, output_file, stage_width, stage_height, reserve_blank=0, font_face='sans-serif', font_size=25.0, text_opacity=1.0, comment_duration=5.0, is_reduce_comments=False, progress_callback=None): 443 | fo = None 444 | comments = ReadComments(input_files, font_size) 445 | try: 446 | fo = ConvertToFile(output_file, 'w') 447 | ProcessComments(comments, fo, stage_width, stage_height, reserve_blank, font_face, font_size, text_opacity, comment_duration, is_reduce_comments, progress_callback) 448 | finally: 449 | if output_file and fo != output_file: 450 | fo.close() 451 | 452 | def ReadComments(input_files, font_size=25.0): 453 | comments = [] 454 | comments.extend(ReadCommentsBilibili(input_files, font_size)) 455 | comments.sort() 456 | return comments 457 | 458 | 459 | 460 | cid = '{query}' 461 | if cid != '': 462 | t = cid.split('------') 463 | t[1] = t[1].replace(r'/',''); 464 | Danmaku2ASS(GetDanmuku(t[0]),r'%s/Desktop/%s-%s.ass'%(os.path.expanduser('~'),t[1],t[2]), 640, 360, 0, 'sans-serif', 15, 0.5, 10, False) 465 | if t[2] == '1': 466 | print t[1] 467 | else: 468 | print '%s(%s p)'%(t[1],t[2]) -------------------------------------------------------------------------------- /GetAssFromBilibili/source/getVedio.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Mon May 26 23:42:03 2014 4 | 5 | @author: Administrator 6 | """ 7 | 8 | 9 | from support import * 10 | import hashlib 11 | import io 12 | import xml.dom.minidom 13 | import random 14 | import math 15 | import os 16 | import sys 17 | from Feedback import * 18 | 19 | default_encoding = 'utf-8' 20 | if sys.getdefaultencoding() != default_encoding: 21 | reload(sys) 22 | sys.setdefaultencoding(default_encoding) 23 | 24 | 25 | def GetVedioInfo(aid,appkey,page = 1,AppSecret=None,fav = None): 26 | paras = {'id': GetString(aid),'page': GetString(page)}; 27 | if fav != None: 28 | paras['fav'] = fav; 29 | url = 'http://api.bilibili.cn/view?'+GetSign(paras,appkey,AppSecret); 30 | jsoninfo = JsonInfo(url); 31 | vedio = Vedio(aid,jsoninfo.Getvalue('title')); 32 | vedio.guankan = jsoninfo.Getvalue('play') 33 | vedio.commentNumber = jsoninfo.Getvalue('review') 34 | vedio.danmu = jsoninfo.Getvalue('video_review') 35 | vedio.shoucang = jsoninfo.Getvalue('favorites'); 36 | vedio.description = jsoninfo.Getvalue('description') 37 | vedio.tag = []; 38 | taglist = jsoninfo.Getvalue('tag'); 39 | if taglist != None: 40 | for tag in taglist.split(','): 41 | vedio.tag.append(tag); 42 | vedio.cover = jsoninfo.Getvalue('pic'); 43 | vedio.author = User(jsoninfo.Getvalue('mid'),jsoninfo.Getvalue('author')); 44 | vedio.page = jsoninfo.Getvalue('pages'); 45 | vedio.date = jsoninfo.Getvalue('created_at'); 46 | vedio.credit = jsoninfo.Getvalue('credit'); 47 | vedio.coin = jsoninfo.Getvalue('coins'); 48 | vedio.spid = jsoninfo.Getvalue('spid'); 49 | vedio.cid = jsoninfo.Getvalue('cid'); 50 | vedio.offsite = jsoninfo.Getvalue('offsite'); 51 | vedio.partname = jsoninfo.Getvalue('partname'); 52 | vedio.src = jsoninfo.Getvalue('src'); 53 | vedio.tid = jsoninfo.Getvalue('tid') 54 | vedio.typename = jsoninfo.Getvalue('typename') 55 | vedio.instant_server = jsoninfo.Getvalue('instant_server'); 56 | return vedio 57 | 58 | def GetSign(params,appkey,AppSecret=None): 59 | """ 60 | 获取新版API的签名,不然会返回-3错误 61 | 待添加:【重要!】 62 | 需要做URL编码并保证字母都是大写,如 %2F 63 | """ 64 | params['appkey']=appkey; 65 | data = ""; 66 | paras = params.keys(); 67 | paras.sort(); 68 | for para in paras: 69 | if data != "": 70 | data += "&"; 71 | data += para + "=" + params[para]; 72 | if AppSecret == None: 73 | return data 74 | m = hashlib.md5() 75 | m.update(data+AppSecret) 76 | return data+'&sign='+m.hexdigest() 77 | 78 | fb = Feedback() 79 | av = '{query}' 80 | appkey = "03fc8eb101b091fb" 81 | regex = re.compile('http:/*[^/]+/video/av(\\d+)(/|/index.html|/index_(\\d+).html)?(\\?|#|$)') 82 | regex_match = regex.match(av) 83 | if regex_match: 84 | aid = regex_match.group(1) 85 | pid = regex_match.group(3) or '1' 86 | vedio = GetVedioInfo(aid,appkey,AppSecret=None,page = pid) 87 | try: 88 | if pid == '1': 89 | fb.add_item('转化'+vedio.title,subtitle="弹幕cid:%d"%(vedio.cid),arg=str(vedio.cid)+'------'+vedio.title+'------'+pid) 90 | else: 91 | fb.add_item('转化'+vedio.title,subtitle="弹幕cid:%d (%s P)"%(vedio.cid,pid),arg=str(vedio.cid)+'------'+vedio.title+'------'+pid) 92 | 93 | if pid == '1': 94 | fb.add_item('不转化'+vedio.title,subtitle="弹幕cid:%d"%(vedio.cid),arg='') 95 | else: 96 | fb.add_item('不转化'+vedio.title,subtitle="弹幕cid:%d (%s P)"%(vedio.cid,pid),arg='') 97 | except SyntaxError as e: 98 | if ('EOF', 'EOL' in e.msg): 99 | fb.add_item('...') 100 | else: 101 | fb.add_item('SyntaxError', e.msg) 102 | except Exception as e: 103 | fb.add_item(e.__class__.__name__,subtitle=e.message) 104 | print fb 105 | 106 | # Danmaku2ASS(GetDanmuku(vedio.cid),r'%s/Desktop/%s-%s.ass'%(os.path.expanduser('~'),vedio.title,pid), 640, 360, 0, 'sans-serif', 15, 0.5, 10, False) -------------------------------------------------------------------------------- /GetAssFromBilibili/source/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vespa314/BilibiliAlfredWorkFlows/43f3a27d35bd28118bf9935d4236344b2d5d86d7/GetAssFromBilibili/source/icon.png -------------------------------------------------------------------------------- /GetAssFromBilibili/source/support.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Mon May 26 23:59:09 2014 4 | 5 | @author: Administrator 6 | """ 7 | import urllib2 8 | import re 9 | import json 10 | import zlib 11 | from biclass import * 12 | import time 13 | def GetRE(content,regexp): 14 | return re.findall(regexp, content) 15 | 16 | def getURLContent(url): 17 | while True: 18 | flag = 1; 19 | try: 20 | headers = {'User-Agent':'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6'} 21 | req = urllib2.Request(url = url,headers = headers); 22 | content = urllib2.urlopen(req).read(); 23 | except: 24 | flag = 0; 25 | time.sleep(5) 26 | if flag == 1: 27 | break; 28 | return content; 29 | 30 | #def FromJson(url): 31 | # return json.loads(getURLContent(url)) 32 | 33 | class JsonInfo(): 34 | def __init__(self,url): 35 | self.info = json.loads(getURLContent(url)); 36 | def Getvalue(self,*keys): 37 | if len(keys) == 0: 38 | return None 39 | if self.info.has_key(keys[0]): 40 | temp = self.info[keys[0]]; 41 | else: 42 | return None; 43 | if len(keys) > 1: 44 | for key in keys[1:]: 45 | if temp.has_key(key): 46 | temp = temp[key] 47 | else: 48 | return None; 49 | return temp 50 | info = None; 51 | 52 | def GetString(t): 53 | if type(t) == int: 54 | return str(t) 55 | return t 56 | 57 | def getint(string): 58 | try: 59 | i = int(string) 60 | except: 61 | i = 0 62 | return i 63 | 64 | #从视频源码获取视频信息 65 | def GetVedioFromRate(content): 66 | #av号和标题 67 | regular1 = r'([^/]+)'; 68 | info1 = GetRE(content,regular1) 69 | #观看数 70 | regular2 = r'(.+)'; 71 | info2 = GetRE(content,regular2) 72 | #收藏 73 | regular3 = r'(.+)'; 74 | info3 = GetRE(content,regular3) 75 | #弹幕 76 | regular4 = r'(.+)'; 77 | info4 = GetRE(content,regular4) 78 | #日期 79 | regular5 = r'(\d+-\d+-\d+ \d+:\d+)'; 80 | info5 = GetRE(content,regular5) 81 | #封面 82 | regular6 = r''; 83 | info6 = GetRE(content,regular6) 84 | #Up的id和名字 85 | regular7 = r'(.+)' 86 | info7 = GetRE(content,regular7) 87 | #!!!!!!!!这里可以断言所有信息长度相等 88 | vedioNum = len(info1);#视频长度 89 | vedioList = []; 90 | for i in range(vedioNum): 91 | vedio_t = Vedio(); 92 | vedio_t.aid = getint(info1[i][0]); 93 | vedio_t.title = info1[i][1]; 94 | vedio_t.guankan = getint(info2[i]); 95 | vedio_t.shoucang = getint(info3[i]); 96 | vedio_t.danmu = getint(info4[i]); 97 | vedio_t.date = info5[i]; 98 | vedio_t.cover = info6[i]; 99 | vedio_t.author = User(info7[i][0],info7[i][1]) 100 | vedioList.append(vedio_t); 101 | return vedioList 102 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Vespa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## B站搜索 2 | ~~介绍可以参见[博客](http://www.kylen314.com/archives/6670)~~ 3 | 4 | 关键字:`bl` (喂,`bl`是`b`i`l`i的简写啊!) 5 | 6 | ![image](https://github.com/Vespa314/BilibiliAlfredWorkFlows/raw/master/img/bl1.png) 7 | 8 | 如按下文配置好[biligrab-danmaku2ass](https://github.com/m13253/biligrab-danmaku2ass)及[you-get](https://github.com/soimort/you-get),选中搜索结果中的视频: 9 | * 回车直接在默认浏览器中打开视频 10 | * 按住`cmd`然后回车即可使用mpv本地播放视频 11 | * 按住`ctrl`再回车可以查看高清视频;【因为macbook观看弹幕视频发热太过严重,这个方法不仅可以有效解决这个问题,并且新番可以**跳广告**!!】 12 | * 按住`alt`再回车可以下载视频【如果可以的话】; 13 | 14 | 11/07更新: 15 | 按住`shift`可以下载弹幕文件到桌面 16 | ![image](https://github.com/Vespa314/BilibiliAlfredWorkFlows/raw/master/img/bl2.gif) 17 | 18 | 19 | ~~## B站新番 Version 1 【已废弃】~~ 20 | ~~介绍可以参见[博客](http://www.kylen314.com/archives/6670)~~ 21 | 22 | 关键字:`bgm` 23 | 24 | 食用方法: 25 | * 直接输入`bgm`可以查看最近更新的二次元新番 26 | * 后面加`t`可以查看今天会更新的新番 27 | * 输入`wn`可以查看`周n`更新的视频,比如`bgm w3`就是查看周三更新的视频 28 | * 前面的各种命令前面加个`3`可以查看三次元新番 29 | 30 | ![image](https://github.com/Vespa314/BilibiliAlfredWorkFlows/raw/master/img/bgm1.png) 31 | ![image](https://github.com/Vespa314/BilibiliAlfredWorkFlows/raw/master/img/bgm2.png) 32 | ![image](https://github.com/Vespa314/BilibiliAlfredWorkFlows/raw/master/img/bgm3.png) 33 | ![image](https://github.com/Vespa314/BilibiliAlfredWorkFlows/raw/master/img/bgm4.png) 34 | 35 | > 比较讨厌的有两点,一是这些更新顺序我代码中明明是按更新时间顺序输出的,但是显示出来却不全是,貌似跟你最近点击有关。。 36 | >还有一点,获取到的新番信息里面包含封面的URL的,但是好像Alfred列表的每个选项前面的图标只能是本地icon,不然不显示。。 37 | 38 | ## B站新番 Version 2 39 | 由于上一个命令只能打开新番搜索页面,然后试图对选中的新番直接打开所在URL,但是无奈Alfred设计上的问题,无法直接选中新番然后像之前别的方法一般按着`cmd`就可以直接本地播放,于是暂时采用了一个迂回的方法: 40 | 41 | **输入命令`bgm`,然后选中新番,回车!!将会把本地播放的命令(也就是`bili —hd http://www.bilibili.com/video/avxxxxx`)复制到剪贴板,然后打开终端ctrl+V运行即可。(当然的,你需要按照本文末尾方法配制播放环境!!)** 42 | 43 | 演示: 44 | ![image](https://github.com/Vespa314/BilibiliAlfredWorkFlows/raw/master/img/bgm5.gif) 45 | 46 | > 忘了什么时候更新的功能,选中之后`cmd`+回车,会把url(http://www.bilibili.com/video/avxxxxx )复制到剪贴板,使用[这个脚本](https://github.com/Vespa314/bilibili-api/tree/master/GetVedioUrl) 获取视频下载url,就可以直接扔迅雷下载了。。【唉,虽然现在版权越来越严。。】或者使用[弹幕下载的alfred](https://github.com/Vespa314/BilibiliAlfredWorkFlows/tree/master/GetAssFromBilibili)或者[脚本](https://github.com/Vespa314/bilibili-api/tree/master/GetDanmuAss)直接下载弹幕文件到本地【这样就可以在高铁上离线弹幕了!!】 47 | 48 | 演示: 49 | ![image](https://github.com/Vespa314/BilibiliAlfredWorkFlows/raw/master/img/bgm6.gif) 50 | 51 | 52 | ## B站排行 53 | ~~介绍可以参见[博客](http://www.kylen314.com/archives/6670)~~ 54 | 55 | 关键字:`bhot` 56 | 57 | 食用方法: 58 | * 查看热门视频只要输入`bhot`即可[默认返回的是`全站` `3天`内`除了新番区`以外`点击量`最高的`20`个视频;] 59 | * 选择分区,后面加上: 60 | * 动画区:`dh` 61 | * 音乐舞蹈区:`yy` 62 | * 游戏区:`yx` 63 | * 娱乐区:`yl` 64 | * 科学与技术:`kj` 65 | * 设置天数范围只要加个`dx`就可以返回`x天`内排名最高的; 66 | * 排名方式默认点击量,也可以选择: 67 | * 按弹幕数排:`dm` 68 | * 收藏数:`sc` 69 | * 评论数:`pl` 70 | * 硬币数:`yb` 71 | 72 | ![image](https://github.com/Vespa314/BilibiliAlfredWorkFlows/raw/master/img/bhot1.png) 73 | 74 | > 举个例子,你想查看`60天`内`音乐区` `硬币数`最高的视频:那么输入`bhot d60ybyy`【参数顺序随便写,比如`bhot ybyyd60`也可以】 75 | 76 | ![image](https://github.com/Vespa314/BilibiliAlfredWorkFlows/raw/master/img/bhot2.png) 77 | 78 | 如按下文配置好[biligrab-danmaku2ass](https://github.com/m13253/biligrab-danmaku2ass)及[you-get](https://github.com/soimort/you-get),选中搜索结果中的视频: 79 | * 回车直接在默认浏览器中打开视频 80 | * 按住`cmd`然后回车即可使用mpv本地播放视频 81 | * 按住`ctrl`再回车可以查看高清视频;【因为macbook观看弹幕视频发热太过严重,这个方法不仅可以有效解决这个问题,并且新番可以**跳广告**!!】 82 | * 按住`alt`再回车可以下载视频【如果可以的话】; 83 | 84 | 演示: 85 | ![image](https://github.com/Vespa314/BilibiliAlfredWorkFlows/raw/master/img/bhot3.gif) 86 | 87 | ## 获取弹幕ASS文件以供本地播放 88 | 关键字:`danmu` 89 | 90 | 使用方法: 91 | danmu加上视频URL地址即可,比如: 92 | * danmu http://www.bilibili.com/video/av510515/index_3.html 93 | * danmu http://www.bilibili.com/video/av1687261/ 94 | 95 | 然后转换成功的话可以获得ASS文件,请到桌面查找 96 | 97 | > 有些时候一些新番的的弹幕需要登录才可以获取,这里没有实现。。。也不会去实现。。 98 | 99 | ## 获取视频下载url 100 | 关键字:`url` 101 | 102 | 食用方法: 103 | url加上视频URL地址即可,比如: 104 | * url http://www.bilibili.com/video/av1687261/ 105 | 106 | 选中后会自动将将url复制到剪贴板,可以直接打开迅雷粘贴即可!! 107 | 108 | ![image](https://github.com/Vespa314/BilibiliAlfredWorkFlows/raw/master/img/url1.png) 109 | 110 | > 注:如果出现多个下载url,最好不要使用这个方法,而采用you-get之类的会自动合并多个短视频的脚本。 111 | 112 | ## 转化xml弹幕文件为ass文件 113 | 关键字:`xml2ass` 114 | 115 | 食用方法: 116 | 直接xml2ass,会读取桌面xml文件供你选择,选中即转化为同名ass文件,拖入播放器即可。 117 | 118 | ![image](https://github.com/Vespa314/BilibiliAlfredWorkFlows/raw/master/img/xml2ass.png) 119 | 120 | > 问题一:有了danmu这个Alfred为什么还要写这个?因为B站封appkey,danmu必须借助appkey+av号获取到视频的cid,才可以自动获取到xml文件,现在没有appkey的用户自然获取不到xml文件。 121 | > 问题二:那么如何获取xml文件呢?请使用[bilibili-mac-client](https://github.com/typcn/bilibili-mac-client) 122 | > 问题三:请问有了[bilibili-mac-client](https://github.com/typcn/bilibili-mac-client),要你这个Alfred何用?额。。假设你能容忍暗牧的存在的话,或者B站有些视频低画质之类的。。。这个是没什么用。。 123 | 124 | ## 配合使用的系统设置 125 | 首先完成以下三项配置: 126 | * [you-get](https://github.com/soimort/you-get) 127 | * [biligrab-danmaku2ass](https://github.com/m13253/biligrab-danmaku2ass) 128 | * [danmaku2ass](https://github.com/m13253/danmaku2ass) 129 | 130 | 目标: 131 | * 可以使用`you-get http://www.bilibili.com/video/avXXXX`下载B站视频 132 | * 假设cd到[biligrab-danmaku2ass](https://github.com/m13253/biligrab-danmaku2ass)所放的路径,可以使用`./bilidan.py http://www.bilibili.com/video/avXXX/`在MPV中本地播放视频 133 | 134 | 然后: 135 | 在'\usr\bin'中建立一个名为`bili`的文本文件,内容如下: 136 | ``` 137 | #!/bin/bash 138 | xxxxxxx/bilidan.py $@ 139 | ``` 140 | 其中前面`xxxxxxx`表示`bilidan.py`路径,然后添加执行权限`sudo chmod +x bili`后便可以随时随地在任意路径下终端使用`bili http://www.bilibili.com/video/avXXX/`播放视频了!! 141 | 当然你也可以设置默认配置,比如弹幕透明度: 142 | ``` 143 | #!/bin/bash 144 | ~/danmaku2ass/bilidan.py --d2aflags 'text_opacity=0.5' $@ 145 | ``` 146 | 完成以上配置即可!! -------------------------------------------------------------------------------- /img/bgm1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vespa314/BilibiliAlfredWorkFlows/43f3a27d35bd28118bf9935d4236344b2d5d86d7/img/bgm1.png -------------------------------------------------------------------------------- /img/bgm2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vespa314/BilibiliAlfredWorkFlows/43f3a27d35bd28118bf9935d4236344b2d5d86d7/img/bgm2.png -------------------------------------------------------------------------------- /img/bgm3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vespa314/BilibiliAlfredWorkFlows/43f3a27d35bd28118bf9935d4236344b2d5d86d7/img/bgm3.png -------------------------------------------------------------------------------- /img/bgm4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vespa314/BilibiliAlfredWorkFlows/43f3a27d35bd28118bf9935d4236344b2d5d86d7/img/bgm4.png -------------------------------------------------------------------------------- /img/bgm5.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vespa314/BilibiliAlfredWorkFlows/43f3a27d35bd28118bf9935d4236344b2d5d86d7/img/bgm5.gif -------------------------------------------------------------------------------- /img/bgm6.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vespa314/BilibiliAlfredWorkFlows/43f3a27d35bd28118bf9935d4236344b2d5d86d7/img/bgm6.gif -------------------------------------------------------------------------------- /img/bhot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vespa314/BilibiliAlfredWorkFlows/43f3a27d35bd28118bf9935d4236344b2d5d86d7/img/bhot1.png -------------------------------------------------------------------------------- /img/bhot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vespa314/BilibiliAlfredWorkFlows/43f3a27d35bd28118bf9935d4236344b2d5d86d7/img/bhot2.png -------------------------------------------------------------------------------- /img/bhot3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vespa314/BilibiliAlfredWorkFlows/43f3a27d35bd28118bf9935d4236344b2d5d86d7/img/bhot3.gif -------------------------------------------------------------------------------- /img/bl1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vespa314/BilibiliAlfredWorkFlows/43f3a27d35bd28118bf9935d4236344b2d5d86d7/img/bl1.png -------------------------------------------------------------------------------- /img/bl2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vespa314/BilibiliAlfredWorkFlows/43f3a27d35bd28118bf9935d4236344b2d5d86d7/img/bl2.gif -------------------------------------------------------------------------------- /img/url1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vespa314/BilibiliAlfredWorkFlows/43f3a27d35bd28118bf9935d4236344b2d5d86d7/img/url1.png -------------------------------------------------------------------------------- /img/xml2ass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vespa314/BilibiliAlfredWorkFlows/43f3a27d35bd28118bf9935d4236344b2d5d86d7/img/xml2ass.png -------------------------------------------------------------------------------- /xml2ass/source/Feedback.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Fri Sep 26 00:07:49 2014 4 | 5 | @author: vespa 6 | """ 7 | import sys 8 | 9 | import xml.etree.ElementTree as et 10 | default_encoding = 'utf-8' 11 | if sys.getdefaultencoding() != default_encoding: 12 | reload(sys) 13 | sys.setdefaultencoding(default_encoding) 14 | 15 | class Feedback(): 16 | """Feeback used by Alfred Script Filter 17 | 18 | Usage: 19 | fb = Feedback() 20 | fb.add_item('Hello', 'World') 21 | fb.add_item('Foo', 'Bar') 22 | print fb 23 | 24 | """ 25 | 26 | def __init__(self): 27 | self.feedback = et.Element('items') 28 | 29 | def __repr__(self): 30 | """XML representation used by Alfred 31 | 32 | Returns: 33 | XML string 34 | """ 35 | return et.tostring(self.feedback) 36 | 37 | def add_item(self, title, subtitle = "", arg = "", valid = "yes", autocomplete = "", icon = "icon.png"): 38 | """ 39 | Add item to alfred Feedback 40 | 41 | Args: 42 | title(str): the title displayed by Alfred 43 | Keyword Args: 44 | subtitle(str): the subtitle displayed by Alfred 45 | arg(str): the value returned by alfred when item is selected 46 | valid(str): whether or not the entry can be selected in Alfred to trigger an action 47 | autcomplete(str): the text to be inserted if an invalid item is selected. This is only used if 'valid' is 'no' 48 | icon(str): filename of icon that Alfred will display 49 | """ 50 | item = et.SubElement(self.feedback, 'item', uid=str(len(self.feedback)), arg=arg, valid=valid, autocomplete=autocomplete) 51 | _title = et.SubElement(item, 'title') 52 | _title.text = title 53 | _sub = et.SubElement(item, 'subtitle') 54 | _sub.text = subtitle 55 | _icon = et.SubElement(item, 'icon') 56 | _icon.text = icon -------------------------------------------------------------------------------- /xml2ass/source/GetAssDanmaku.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Mon May 26 23:42:03 2014 4 | 5 | @author: Administrator 6 | """ 7 | 8 | 9 | from support import * 10 | import hashlib 11 | import io 12 | import xml.dom.minidom 13 | import random 14 | import math 15 | import os 16 | import sys 17 | 18 | default_encoding = 'utf-8' 19 | if sys.getdefaultencoding() != default_encoding: 20 | reload(sys) 21 | sys.setdefaultencoding(default_encoding) 22 | 23 | class safe_list(list): 24 | def get(self, index, default=None): 25 | try: 26 | return self[index] 27 | except IndexError: 28 | return default 29 | 30 | # Calculation is based on https://github.com/jabbany/CommentCoreLibrary/issues/5#issuecomment-40087282 31 | # and https://github.com/m13253/danmaku2ass/issues/7#issuecomment-41489422 32 | # ASS FOV = width*4/3.0 33 | # But Flash FOV = width/math.tan(100*math.pi/360.0)/2 will be used instead 34 | # Result: (transX, transY, rotX, rotY, rotZ, scaleX, scaleY) 35 | def ConvertFlashRotation(rotY, rotZ, X, Y, width, height): 36 | def WrapAngle(deg): 37 | return 180-((180-deg) % 360) 38 | rotY = WrapAngle(rotY) 39 | rotZ = WrapAngle(rotZ) 40 | if rotY in (90, -90): 41 | rotY -= 1 42 | if rotY == 0 or rotZ == 0: 43 | outX = 0 44 | outY = -rotY # Positive value means clockwise in Flash 45 | outZ = -rotZ 46 | rotY *= math.pi/180.0 47 | rotZ *= math.pi/180.0 48 | else: 49 | rotY *= math.pi/180.0 50 | rotZ *= math.pi/180.0 51 | outY = math.atan2(-math.sin(rotY)*math.cos(rotZ), math.cos(rotY))*180/math.pi 52 | outZ = math.atan2(-math.cos(rotY)*math.sin(rotZ), math.cos(rotZ))*180/math.pi 53 | outX = math.asin(math.sin(rotY)*math.sin(rotZ))*180/math.pi 54 | trX = (X*math.cos(rotZ)+Y*math.sin(rotZ))/math.cos(rotY)+(1-math.cos(rotZ)/math.cos(rotY))*width/2-math.sin(rotZ)/math.cos(rotY)*height/2 55 | trY = Y*math.cos(rotZ)-X*math.sin(rotZ)+math.sin(rotZ)*width/2+(1-math.cos(rotZ))*height/2 56 | trZ = (trX-width/2)*math.sin(rotY) 57 | FOV = width*math.tan(2*math.pi/9.0)/2 58 | try: 59 | scaleXY = FOV/(FOV+trZ) 60 | except ZeroDivisionError: 61 | logging.error('Rotation makes object behind the camera: trZ == %.0f' % trZ) 62 | scaleXY = 1 63 | trX = (trX-width/2)*scaleXY+width/2 64 | trY = (trY-height/2)*scaleXY+height/2 65 | if scaleXY < 0: 66 | scaleXY = -scaleXY 67 | outX += 180 68 | outY += 180 69 | logging.error('Rotation makes object behind the camera: trZ == %.0f < %.0f' % (trZ, FOV)) 70 | return (trX, trY, WrapAngle(outX), WrapAngle(outY), WrapAngle(outZ), scaleXY*100, scaleXY*100) 71 | 72 | 73 | def WriteCommentBilibiliPositioned(f, c, width, height, styleid): 74 | #BiliPlayerSize = (512, 384) # Bilibili player version 2010 75 | #BiliPlayerSize = (540, 384) # Bilibili player version 2012 76 | BiliPlayerSize = (672, 438) # Bilibili player version 2014 77 | ZoomFactor = GetZoomFactor(BiliPlayerSize, (width, height)) 78 | 79 | def GetPosition(InputPos, isHeight): 80 | isHeight = int(isHeight) # True -> 1 81 | if isinstance(InputPos, int): 82 | return ZoomFactor[0]*InputPos+ZoomFactor[isHeight+1] 83 | elif isinstance(InputPos, float): 84 | if InputPos > 1: 85 | return ZoomFactor[0]*InputPos+ZoomFactor[isHeight+1] 86 | else: 87 | return BiliPlayerSize[isHeight]*ZoomFactor[0]*InputPos+ZoomFactor[isHeight+1] 88 | else: 89 | try: 90 | InputPos = int(InputPos) 91 | except ValueError: 92 | InputPos = float(InputPos) 93 | return GetPosition(InputPos, isHeight) 94 | 95 | try: 96 | comment_args = safe_list(json.loads(c[3])) 97 | text = ASSEscape(str(comment_args[4]).replace('/n', '\n')) 98 | from_x = comment_args.get(0, 0) 99 | from_y = comment_args.get(1, 0) 100 | to_x = comment_args.get(7, from_x) 101 | to_y = comment_args.get(8, from_y) 102 | from_x = GetPosition(from_x, False) 103 | from_y = GetPosition(from_y, True) 104 | to_x = GetPosition(to_x, False) 105 | to_y = GetPosition(to_y, True) 106 | alpha = safe_list(str(comment_args.get(2, '1')).split('-')) 107 | from_alpha = float(alpha.get(0, 1)) 108 | to_alpha = float(alpha.get(1, from_alpha)) 109 | from_alpha = 255-round(from_alpha*255) 110 | to_alpha = 255-round(to_alpha*255) 111 | rotate_z = int(comment_args.get(5, 0)) 112 | rotate_y = int(comment_args.get(6, 0)) 113 | lifetime = float(comment_args.get(3, 4500)) 114 | duration = int(comment_args.get(9, lifetime*1000)) 115 | delay = int(comment_args.get(10, 0)) 116 | fontface = comment_args.get(12) 117 | isborder = comment_args.get(11, 'true') 118 | from_rotarg = ConvertFlashRotation(rotate_y, rotate_z, from_x, from_y, width, height) 119 | to_rotarg = ConvertFlashRotation(rotate_y, rotate_z, to_x, to_y, width, height) 120 | styles = ['\\org(%d, %d)' % (width/2, height/2)] 121 | if from_rotarg[0:2] == to_rotarg[0:2]: 122 | styles.append('\\pos(%.0f, %.0f)' % (from_rotarg[0:2])) 123 | else: 124 | styles.append('\\move(%.0f, %.0f, %.0f, %.0f, %.0f, %.0f)' % (from_rotarg[0:2]+to_rotarg[0:2]+(delay, delay+duration))) 125 | styles.append('\\frx%.0f\\fry%.0f\\frz%.0f\\fscx%.0f\\fscy%.0f' % (from_rotarg[2:7])) 126 | if (from_x, from_y) != (to_x, to_y): 127 | styles.append('\\t(%d, %d, ' % (delay, delay+duration)) 128 | styles.append('\\frx%.0f\\fry%.0f\\frz%.0f\\fscx%.0f\\fscy%.0f' % (to_rotarg[2:7])) 129 | styles.append(')') 130 | if fontface: 131 | styles.append('\\fn%s' % ASSEscape(fontface)) 132 | styles.append('\\fs%.0f' % (c[6]*ZoomFactor[0])) 133 | if c[5] != 0xffffff: 134 | styles.append('\\c&H%s&' % ConvertColor(c[5])) 135 | if c[5] == 0x000000: 136 | styles.append('\\3c&HFFFFFF&') 137 | if from_alpha == to_alpha: 138 | styles.append('\\alpha&H%02X' % from_alpha) 139 | elif (from_alpha, to_alpha) == (255, 0): 140 | styles.append('\\fad(%.0f,0)' % (lifetime*1000)) 141 | elif (from_alpha, to_alpha) == (0, 255): 142 | styles.append('\\fad(0, %.0f)' % (lifetime*1000)) 143 | else: 144 | styles.append('\\fade(%(from_alpha)d, %(to_alpha)d, %(to_alpha)d, 0, %(end_time).0f, %(end_time).0f, %(end_time).0f)' % {'from_alpha': from_alpha, 'to_alpha': to_alpha, 'end_time': lifetime*1000}) 145 | if isborder == 'false': 146 | styles.append('\\bord0') 147 | f.write('Dialogue: -1,%(start)s,%(end)s,%(styleid)s,,0,0,0,,{%(styles)s}%(text)s\n' % {'start': ConvertTimestamp(c[0]), 'end': ConvertTimestamp(c[0]+lifetime), 'styles': ''.join(styles), 'text': text, 'styleid': styleid}) 148 | except (IndexError, ValueError) as e: 149 | try: 150 | logging.warning(_('Invalid comment: %r') % c[3]) 151 | except IndexError: 152 | logging.warning(_('Invalid comment: %r') % c) 153 | 154 | # Result: (f, dx, dy) 155 | # To convert: NewX = f*x+dx, NewY = f*y+dy 156 | def GetZoomFactor(SourceSize, TargetSize): 157 | try: 158 | if (SourceSize, TargetSize) == GetZoomFactor.Cached_Size: 159 | return GetZoomFactor.Cached_Result 160 | except AttributeError: 161 | pass 162 | GetZoomFactor.Cached_Size = (SourceSize, TargetSize) 163 | try: 164 | SourceAspect = SourceSize[0]/SourceSize[1] 165 | TargetAspect = TargetSize[0]/TargetSize[1] 166 | if TargetAspect < SourceAspect: # narrower 167 | ScaleFactor = TargetSize[0]/SourceSize[0] 168 | GetZoomFactor.Cached_Result = (ScaleFactor, 0, (TargetSize[1]-TargetSize[0]/SourceAspect)/2) 169 | elif TargetAspect > SourceAspect: # wider 170 | ScaleFactor = TargetSize[1]/SourceSize[1] 171 | GetZoomFactor.Cached_Result = (ScaleFactor, (TargetSize[0]-TargetSize[1]*SourceAspect)/2, 0) 172 | else: 173 | GetZoomFactor.Cached_Result = (TargetSize[0]/SourceSize[0], 0, 0) 174 | return GetZoomFactor.Cached_Result 175 | except ZeroDivisionError: 176 | GetZoomFactor.Cached_Result = (1, 0, 0) 177 | return GetZoomFactor.Cached_Result 178 | 179 | 180 | def WriteASSHead(f, width, height, fontface, fontsize, alpha, styleid): 181 | f.write( 182 | ''' 183 | [Script Info] 184 | ; Script generated by Danmaku2ASS 185 | ; https://github.com/m13253/danmaku2ass 186 | Script Updated By: Danmaku2ASS (https://github.com/m13253/danmaku2ass) 187 | ScriptType: v4.00+ 188 | PlayResX: %(width)d 189 | PlayResY: %(height)d 190 | Aspect Ratio: %(width)d:%(height)d 191 | Collisions: Normal 192 | WrapStyle: 2 193 | ScaledBorderAndShadow: yes 194 | YCbCr Matrix: TV.601 195 | 196 | [V4+ Styles] 197 | Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding 198 | Style: %(styleid)s, %(fontface)s, %(fontsize).0f, &H%(alpha)02XFFFFFF, &H%(alpha)02XFFFFFF, &H%(alpha)02X000000, &H%(alpha)02X000000, 0, 0, 0, 0, 100, 100, 0.00, 0.00, 1, %(outline).0f, 0, 7, 0, 0, 0, 0 199 | 200 | [Events] 201 | Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text 202 | ''' % {'width': width, 'height': height, 'fontface': fontface, 'fontsize': fontsize, 'alpha': 255-round(alpha*255), 'outline': max(fontsize/25.0, 1), 'styleid': styleid} 203 | ) 204 | 205 | def TestFreeRows(rows, c, row, width, height, bottomReserved, lifetime): 206 | res = 0 207 | rowmax = height-bottomReserved 208 | targetRow = None 209 | if c[4] in (1, 2): 210 | while row < rowmax and res < c[7]: 211 | if targetRow != rows[c[4]][row]: 212 | targetRow = rows[c[4]][row] 213 | if targetRow and targetRow[0]+lifetime > c[0]: 214 | break 215 | row += 1 216 | res += 1 217 | else: 218 | try: 219 | thresholdTime = c[0]-lifetime*(1-width/(c[8]+width)) 220 | except ZeroDivisionError: 221 | thresholdTime = c[0]-lifetime 222 | while row < rowmax and res < c[7]: 223 | if targetRow != rows[c[4]][row]: 224 | targetRow = rows[c[4]][row] 225 | try: 226 | if targetRow and (targetRow[0] > thresholdTime or targetRow[0]+targetRow[8]*lifetime/(targetRow[8]+width) > c[0]): 227 | break 228 | except ZeroDivisionError: 229 | pass 230 | row += 1 231 | res += 1 232 | return res 233 | 234 | def MarkCommentRow(rows, c, row): 235 | row = int(row) 236 | try: 237 | for i in range(row, int(row+math.ceil(c[7]))): 238 | rows[c[4]][i] = c 239 | except IndexError: 240 | pass 241 | 242 | def ASSEscape(s): 243 | def ReplaceLeadingSpace(s): 244 | sstrip = s.strip(' ') 245 | slen = len(s) 246 | if slen == len(sstrip): 247 | return s 248 | else: 249 | llen = slen-len(s.lstrip(' ')) 250 | rlen = slen-len(s.rstrip(' ')) 251 | return ''.join(('\u2007'*llen, sstrip, '\u2007'*rlen)) 252 | return '\\N'.join((ReplaceLeadingSpace(i) or ' ' for i in str(s).replace('\\', '\\\\').replace('{', '\\{').replace('}', '\\}').split('\n'))) 253 | 254 | def ConvertTimestamp(timestamp): 255 | timestamp = round(timestamp*100.0) 256 | hour, minute = divmod(timestamp, 360000) 257 | minute, second = divmod(minute, 6000) 258 | second, centsecond = divmod(second, 100) 259 | return '%d:%02d:%02d.%02d' % (int(hour), int(minute), int(second), int(centsecond)) 260 | 261 | def ConvertType2(row, height, bottomReserved): 262 | return height-bottomReserved-row 263 | 264 | def FindAlternativeRow(rows, c, height, bottomReserved): 265 | res = 0 266 | for row in range(int(height-bottomReserved-math.ceil(c[7]))): 267 | if not rows[c[4]][row]: 268 | return row 269 | elif rows[c[4]][row][0] < rows[c[4]][res][0]: 270 | res = row 271 | return res 272 | 273 | def ConvertColor(RGB, width=1280, height=576): 274 | if RGB == 0x000000: 275 | return '000000' 276 | elif RGB == 0xffffff: 277 | return 'FFFFFF' 278 | R = (RGB >> 16) & 0xff 279 | G = (RGB >> 8) & 0xff 280 | B = RGB & 0xff 281 | if width < 1280 and height < 576: 282 | return '%02X%02X%02X' % (B, G, R) 283 | else: # VobSub always uses BT.601 colorspace, convert to BT.709 284 | ClipByte = lambda x: 255 if x > 255 else 0 if x < 0 else round(x) 285 | return '%02X%02X%02X' % ( 286 | ClipByte(R*0.00956384088080656+G*0.03217254540203729+B*0.95826361371715607), 287 | ClipByte(R*-0.10493933142075390+G*1.17231478191855154+B*-0.06737545049779757), 288 | ClipByte(R*0.91348912373987645+G*0.07858536372532510+B*0.00792551253479842) 289 | ) 290 | 291 | def WriteComment(f, c, row, width, height, bottomReserved, fontsize, lifetime, styleid): 292 | text = ASSEscape(c[3]) 293 | styles = [] 294 | if c[4] == 1: 295 | styles.append('\\an8\\pos(%(halfwidth)d, %(row)d)' % {'halfwidth': width/2, 'row': row}) 296 | elif c[4] == 2: 297 | styles.append('\\an2\\pos(%(halfwidth)d, %(row)d)' % {'halfwidth': width/2, 'row': ConvertType2(row, height, bottomReserved)}) 298 | elif c[4] == 3: 299 | styles.append('\\move(%(neglen)d, %(row)d, %(width)d, %(row)d)' % {'width': width, 'row': row, 'neglen': -math.ceil(c[8])}) 300 | else: 301 | styles.append('\\move(%(width)d, %(row)d, %(neglen)d, %(row)d)' % {'width': width, 'row': row, 'neglen': -math.ceil(c[8])}) 302 | if not (-1 < c[6]-fontsize < 1): 303 | styles.append('\\fs%.0f' % c[6]) 304 | if c[5] != 0xffffff: 305 | styles.append('\\c&H%s&' % ConvertColor(c[5])) 306 | if c[5] == 0x000000: 307 | styles.append('\\3c&HFFFFFF&') 308 | ## 替换空格 309 | text = text.replace('\u2007',' ') 310 | f.write('Dialogue: 2,%(start)s,%(end)s,%(styleid)s,,0000,0000,0000,,{%(styles)s}%(text)s\n' % {'start': ConvertTimestamp(c[0]), 'end': ConvertTimestamp(c[0]+lifetime), 'styles': ''.join(styles), 'text': text, 'styleid': styleid}) 311 | 312 | 313 | def CalculateLength(s): 314 | return max(map(len, s.split('\n'))) # May not be accurate 315 | 316 | def GetVideoInfo(aid,appkey,page = 1,AppSecret=None,fav = None): 317 | paras = {'id': GetString(aid),'page': GetString(page)} 318 | if fav != None: 319 | paras['fav'] = fav 320 | url = 'http://api.bilibili.cn/view?'+GetSign(paras,appkey,AppSecret) 321 | jsoninfo = JsonInfo(url) 322 | video = Video(aid,jsoninfo.Getvalue('title')) 323 | video.guankan = jsoninfo.Getvalue('play') 324 | video.commentNumber = jsoninfo.Getvalue('review') 325 | video.danmu = jsoninfo.Getvalue('video_review') 326 | video.shoucang = jsoninfo.Getvalue('favorites') 327 | video.description = jsoninfo.Getvalue('description') 328 | video.tag = [] 329 | taglist = jsoninfo.Getvalue('tag') 330 | if taglist != None: 331 | for tag in taglist.split(','): 332 | video.tag.append(tag) 333 | video.cover = jsoninfo.Getvalue('pic') 334 | video.author = User(jsoninfo.Getvalue('mid'),jsoninfo.Getvalue('author')) 335 | video.page = jsoninfo.Getvalue('pages') 336 | video.date = jsoninfo.Getvalue('created_at') 337 | video.credit = jsoninfo.Getvalue('credit') 338 | video.coin = jsoninfo.Getvalue('coins') 339 | video.spid = jsoninfo.Getvalue('spid') 340 | video.cid = jsoninfo.Getvalue('cid') 341 | video.offsite = jsoninfo.Getvalue('offsite') 342 | video.partname = jsoninfo.Getvalue('partname') 343 | video.src = jsoninfo.Getvalue('src') 344 | video.tid = jsoninfo.Getvalue('tid') 345 | video.typename = jsoninfo.Getvalue('typename') 346 | video.instant_server = jsoninfo.Getvalue('instant_server') 347 | return video 348 | 349 | def GetSign(params,appkey,AppSecret=None): 350 | """ 351 | 获取新版API的签名,不然会返回-3错误 352 | 待添加:【重要!】 353 | 需要做URL编码并保证字母都是大写,如 %2F 354 | """ 355 | params['appkey']=appkey 356 | data = "" 357 | paras = params.keys() 358 | paras.sort() 359 | for para in paras: 360 | if data != "": 361 | data += "&" 362 | data += para + "=" + params[para] 363 | if AppSecret == None: 364 | return data 365 | m = hashlib.md5() 366 | m.update(data+AppSecret) 367 | return data+'&sign='+m.hexdigest() 368 | 369 | 370 | 371 | 372 | def GetDanmuku(cid): 373 | cid = getint(cid) 374 | url = "http://comment.bilibili.cn/%d.xml"%(cid) 375 | content = zlib.decompressobj(-zlib.MAX_WBITS).decompress(getURLContent(url)) 376 | # content = GetRE(content,r']*>([^<]*)<') 377 | return content 378 | 379 | 380 | #def FilterBadChars(f): 381 | # s = f.read() 382 | # s = re.sub('[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f]', '\ufffd', s) 383 | # return io.StringIO(s) 384 | 385 | def ReadCommentsBilibili(f, fontsize): 386 | dom = xml.dom.minidom.parseString(f) 387 | comment_element = dom.getElementsByTagName('d') 388 | for i, comment in enumerate(comment_element): 389 | try: 390 | p = str(comment.getAttribute('p')).split(',') 391 | assert len(p) >= 5 392 | assert p[1] in ('1', '4', '5', '6', '7') 393 | if p[1] != '7': 394 | c = str(comment.childNodes[0].wholeText).replace('/n', '\n') 395 | size = int(p[2])*fontsize/25.0 396 | yield (float(p[0]), int(p[4]), i, c, {'1': 0, '4': 2, '5': 1, '6': 3}[p[1]], int(p[3]), size, (c.count('\n')+1)*size, CalculateLength(c)*size) 397 | else: # positioned comment 398 | c = str(comment.childNodes[0].wholeText) 399 | yield (float(p[0]), int(p[4]), i, c, 'bilipos', int(p[3]), int(p[2]), 0, 0) 400 | except (AssertionError, AttributeError, IndexError, TypeError, ValueError): 401 | continue 402 | 403 | def ConvertToFile(filename_or_file, *args, **kwargs): 404 | return open(filename_or_file, *args, **kwargs) 405 | 406 | 407 | def ProcessComments(comments, f, width, height, bottomReserved, fontface, fontsize, alpha, lifetime, reduced, progress_callback): 408 | styleid = 'Danmaku2ASS_%04x' % random.randint(0, 0xffff) 409 | WriteASSHead(f, width, height, fontface, fontsize, alpha, styleid) 410 | rows = [[None]*(height-bottomReserved+1) for i in range(4)] 411 | for idx, i in enumerate(comments): 412 | if progress_callback and idx % 1000 == 0: 413 | progress_callback(idx, len(comments)) 414 | if isinstance(i[4], int): 415 | row = 0 416 | rowmax = height-bottomReserved-i[7] 417 | while row <= rowmax: 418 | freerows = TestFreeRows(rows, i, row, width, height, bottomReserved, lifetime) 419 | if freerows >= i[7]: 420 | MarkCommentRow(rows, i, row) 421 | WriteComment(f, i, row, width, height, bottomReserved, fontsize, lifetime, styleid) 422 | break 423 | else: 424 | row += freerows or 1 425 | else: 426 | if not reduced: 427 | row = FindAlternativeRow(rows, i, height, bottomReserved) 428 | MarkCommentRow(rows, i, row) 429 | WriteComment(f, i, row, width, height, bottomReserved, fontsize, lifetime, styleid) 430 | elif i[4] == 'bilipos': 431 | WriteCommentBilibiliPositioned(f, i, width, height, styleid) 432 | elif i[4] == 'acfunpos': 433 | WriteCommentAcfunPositioned(f, i, width, height, styleid) 434 | elif i[4] == 'sH5Vpos': 435 | WriteCommentSH5VPositioned(f, i, width, height, styleid) 436 | else: 437 | logging.warning(_('Invalid comment: %r') % i[3]) 438 | if progress_callback: 439 | progress_callback(len(comments), len(comments)) 440 | 441 | def ReadComments(input_files, font_size=25.0): 442 | comments = [] 443 | comments.extend(ReadCommentsBilibili(input_files, font_size)) 444 | comments.sort() 445 | return comments 446 | 447 | -------------------------------------------------------------------------------- /xml2ass/source/biclass.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Wed May 28 01:22:20 2014 4 | 5 | @author: Vespa 6 | """ 7 | 8 | class User(): 9 | def __init__(self,m_mid=None,m_name=None): 10 | if m_mid: 11 | self.mid = m_mid 12 | if m_name: 13 | if isinstance(m_name,unicode): 14 | m_name = m_name.encode('utf8') 15 | self.name = m_name 16 | # 获取空间地址 17 | def GetSpace(self): 18 | return 'http://space.bilibili.tv/'+str(self.mid) 19 | mid = None 20 | name = None 21 | isApprove = None#是否是认证账号 22 | spaceName = None 23 | sex = None 24 | rank = None 25 | avatar = None 26 | follow = None#关注好友数目 27 | fans = None#粉丝数目 28 | article = None#投稿数 29 | place = None#所在地 30 | description = None#认证用户为认证信息 普通用户为交友宣言 31 | followlist = None#关注的好友列表 32 | friend = None 33 | DisplayRank = None 34 | message = None # 承包时会返回的承保信息 35 | 36 | 37 | class Video(): 38 | def __init__(self,m_aid=None,m_title=None): 39 | if m_aid: 40 | self.aid = m_aid 41 | if m_title: 42 | if isinstance(m_title,unicode): 43 | m_title = m_title.encode('utf8') 44 | self.title = m_title 45 | aid = None 46 | title = None 47 | guankan = None 48 | shoucang = None 49 | danmu = None 50 | date = None 51 | cover = None 52 | commentNumber = None 53 | description = None 54 | tag = None 55 | author = None 56 | page = None 57 | credit = None 58 | coin = None 59 | spid = None 60 | cid = None 61 | offsite = None#Flash播放调用地址 62 | Iscopy = None 63 | subtitle = None 64 | duration = None 65 | episode = None 66 | arcurl = None#网页地址 67 | arcrank = None#不明 68 | tid = None 69 | typename = None 70 | online_user = None # 当前在线观看人数 71 | #不明: 72 | instant_server = None 73 | src = None 74 | partname = None 75 | allow_bp = None 76 | allow_feed = None 77 | created = None 78 | #播放信息: 79 | play_site = None 80 | play_forward = None 81 | play_mobile = None 82 | 83 | class Bangumi(): 84 | def __init__(self): 85 | pass 86 | typeid = None 87 | lastupdate = None 88 | areaid = None 89 | bgmcount = None#番剧当前总集数 90 | title = None 91 | lastupdate_at = None 92 | attention = None #订阅数 93 | cover = None 94 | priority = None 95 | area = None 96 | weekday = None 97 | spid = None 98 | new = None 99 | scover = None 100 | mcover = None 101 | click = None 102 | season_id = None 103 | click = None # 浏览数 104 | video_view = None 105 | 106 | class Comment(): 107 | def __init__(self): 108 | self.post_user = User() 109 | lv = None#楼层 110 | fbid = None#评论id 111 | parent_id = None #被回复留言ID 112 | msg = None 113 | ad_check = None#状态 (0: 正常 1: UP主隐藏 2: 管理员删除 3: 因举报删除) 114 | post_user = None 115 | like = None 116 | 117 | class CommentList(): 118 | def __init__(self): 119 | pass 120 | comments = None 121 | commentLen = None 122 | page = None 123 | 124 | class ZhuantiInfo(): 125 | def __init__(self, m_spid,m_title): 126 | self.spid = m_spid 127 | if isinstance(m_title,unicode): 128 | m_title = m_title.encode('utf8') 129 | self.title = m_title 130 | spid = None 131 | title = None 132 | author = None 133 | cover = None 134 | thumb = None 135 | ischeck = None #不明 136 | typeurl = None #总是"http://www.bilibili.com" 137 | tag = None 138 | description = None 139 | pubdate = None # 不明 140 | postdate = None 141 | lastupdate = None 142 | click = None 143 | favourite = None 144 | attention = None 145 | count = None 146 | bgmcount = None 147 | spcount = None 148 | season_id = None 149 | is_bangumi = None 150 | arcurl = None 151 | is_bangumi_end = None 152 | 153 | class Danmu(): 154 | def __init__(self): 155 | pass 156 | t_video = None 157 | t_stamp = None 158 | mid_crc = None # 值为:hex(binascii.crc32(mid)) 159 | danmu_type = None # 1:滚动弹幕 5:顶端弹幕 4:底部弹幕 160 | content = None 161 | danmu_color = None 162 | danmu_fontsize = None 163 | 164 | class SponsorInfo(): 165 | def __init__(self): 166 | pass 167 | bp = None ## 剧番总B币 168 | percent = None #承包总比例,不知道什么鬼 169 | ep_bp = None ## 该话的B币 170 | ep_percent = None ## 不明 171 | sponsor_num = None ## 承包人数 172 | sponsor_user = None ## 承包人列表 173 | 174 | class LivingInfo(): 175 | def __init__(self): 176 | pass 177 | url = None 178 | title = None 179 | cover = None 180 | mid = None -------------------------------------------------------------------------------- /xml2ass/source/bilibili.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Mon May 26 23:42:03 2014 4 | 5 | @author: Vespa 6 | """ 7 | 8 | 9 | from support import * 10 | ############################常量定义 11 | 12 | #####排序方式 13 | #收藏 14 | TYPE_SHOUCANG = 'stow' 15 | #评论数 16 | TYPE_PINGLUN = 'review' 17 | #播放数 18 | TYPE_BOFANG = 'hot' 19 | #硬币数 20 | TYPE_YINGBI = 'promote' 21 | #用户评分 22 | TYPE_PINGFEN = 'comment' 23 | #弹幕数 24 | TYPE_DANMU = 'damku' 25 | #拼音 26 | TYPE_PINYIN = 'pinyin' 27 | #投稿时间 28 | TYPE_TOUGAO = 'default' 29 | ############################常量定义结束 30 | 31 | def GetPopularVideo(begintime, endtime, sortType=TYPE_BOFANG, zone=0, page=1, original=0): 32 | """ 33 | 输入: 34 | begintime:起始时间,三元数组[year1,month1,day1] 35 | endtime:终止时间,三元数组[year2,month2,day2] 36 | sortType:字符串,排序方式,参照TYPE_开头的常量 37 | zone:整数,分区,参照api.md文档说明 38 | page:整数,页数 39 | 返回: 40 | 视频列表,包含AV号,标题,观看数,收藏数,弹幕数,投稿日期,封面,UP的id号和名字 41 | """ 42 | # TYPE_PINYIN和TYPE_TOUGAO情况下zone不可以等于[0,1,3,4,5,36,11,13] 43 | if sortType in [TYPE_PINYIN,TYPE_TOUGAO]: 44 | if zone in [0,1,3,4,5,36,11,13]: 45 | return [] 46 | #判断是否原创 47 | if original: 48 | ori = '-original' 49 | else: 50 | ori = '' 51 | url = 'http://www.bilibili.tv/list/%s-%d-%d-%d-%d-%d~%d-%d-%d%s.html'%(sortType,zone,page,begintime[0],begintime[1],begintime[2],endtime[0],endtime[1],endtime[2],ori) 52 | content = getURLContent(url) 53 | return GetVideoFromRate(content) 54 | 55 | 56 | def GetUserInfo(url): 57 | """ 58 | 由GetUserInfoBymid(mid)或者GetUserInfoByName(name)调用 59 | 返回: 60 | 用户信息 61 | """ 62 | jsoninfo = JsonInfo(url) 63 | user = User(jsoninfo.Getvalue('mid'), jsoninfo.Getvalue('name')) 64 | user.isApprove = jsoninfo.Getvalue('approve') 65 | #b站现在空间名暂时不返回 66 | #user.spaceName = jsoninfo.Getvalue('spacename') 67 | user.sex = jsoninfo.Getvalue('sex') 68 | user.rank = jsoninfo.Getvalue('rank') 69 | user.avatar = jsoninfo.Getvalue('face') 70 | user.follow = jsoninfo.Getvalue('attention') 71 | user.fans = jsoninfo.Getvalue('fans') 72 | user.article = jsoninfo.Getvalue('article') 73 | user.place = jsoninfo.Getvalue('place') 74 | user.description = jsoninfo.Getvalue('description') 75 | user.friend = jsoninfo.Getvalue('friend') 76 | user.DisplayRank = jsoninfo.Getvalue('DisplayRank') 77 | user.followlist = [] 78 | for fo in jsoninfo.Getvalue('attentions'): 79 | user.followlist.append(fo) 80 | return user 81 | 82 | def GetUserInfoBymid(mid): 83 | """ 84 | 输入: 85 | mid:查询的用户的id 86 | 返回: 87 | 查看GetUserInfo()函数 88 | """ 89 | mid = GetString(mid) 90 | url = 'http://api.bilibili.cn/userinfo'+"?mid="+mid 91 | return GetUserInfo(url) 92 | 93 | def GetUserInfoByName(name): 94 | """ 95 | 输入: 96 | mid:查询的用户的昵称 97 | 返回: 98 | 查看GetUserInfo()函数 99 | """ 100 | name = GetString(name) 101 | url = 'http://api.bilibili.cn/userinfo'+"?user="+name 102 | return GetUserInfo(url) 103 | 104 | def GetVideoOfZhuanti(spid, season_id=None, bangumi=None): 105 | """ 106 | 输入: 107 | spid:专题id 108 | season_id:分季ID 109 | bangumi:设置为1返回剧番,不设置或者设置为0返回相关视频 110 | 返回: 111 | 视频列表,包含av号,标题,封面和观看数 112 | """ 113 | url = 'http://api.bilibili.cn/spview?spid='+GetString(spid) 114 | if season_id: 115 | url += '&season_id='+GetString(season_id) 116 | if bangumi: 117 | url += '&bangumi='+GetString(bangumi) 118 | jsoninfo = JsonInfo(url) 119 | videolist = [] 120 | for video_idx in jsoninfo.Getvalue('list'): 121 | video_idx = DictDecode2UTF8(video_idx) 122 | video = Video(video_idx['aid'],video_idx['title']) 123 | video.cover = video_idx['cover'] 124 | video.guankan = video_idx['click'] 125 | if video_idx.has_key('episode'): 126 | video.episode = video_idx['episode'] 127 | video.src = video_idx["from"] 128 | video.cid = video_idx["cid"] 129 | video.page = video_idx["page"] 130 | videolist.append(video) 131 | return videolist 132 | 133 | def GetComment(aid, page = None, pagesize = None, order = None): 134 | """ 135 | 输入: 136 | aid:AV号 137 | page:页码 138 | pagesize:单页返回的记录条数,最大不超过300,默认为10。 139 | order:排序方式 默认按发布时间倒序 可选:good 按点赞人数排序 hot 按热门回复排序 140 | 返回: 141 | 评论列表 142 | 【注意】:此接口目前只能查询av号小于3280075的视频,url后面增加ver=2或ver=3可以获取到后面视频的『热门评论』,非全部评论,如果需要,请使用GetComment_v2新API 143 | """ 144 | url = 'http://api.bilibili.cn/feedback?aid='+GetString(aid) 145 | if page: 146 | url += '&page='+GetString(page) 147 | if pagesize: 148 | url += '&pagesize='+GetString(pagesize) 149 | if order: 150 | url += '&order='+GetString(order) 151 | print url 152 | jsoninfo = JsonInfo(url) 153 | commentList = CommentList() 154 | commentList.comments = [] 155 | commentList.commentLen = jsoninfo.Getvalue('totalResult') 156 | commentList.page = jsoninfo.Getvalue('pages') 157 | idx = 0 158 | while jsoninfo.Getvalue(str(idx)): 159 | liuyan = Comment() 160 | liuyan.lv = jsoninfo.Getvalue(str(idx),'lv') 161 | liuyan.fbid = jsoninfo.Getvalue(str(idx),'fbid') 162 | liuyan.msg = jsoninfo.Getvalue(str(idx),'msg') 163 | liuyan.ad_check = jsoninfo.Getvalue(str(idx),'ad_check') 164 | liuyan.post_user.mid = jsoninfo.Getvalue(str(idx),'mid') 165 | liuyan.post_user.avatar = jsoninfo.Getvalue(str(idx),'face') 166 | liuyan.post_user.rank = jsoninfo.Getvalue(str(idx),'rank') 167 | liuyan.post_user.name = jsoninfo.Getvalue(str(idx),'nick') 168 | commentList.comments.append(liuyan) 169 | idx += 1 170 | return commentList 171 | 172 | def GetComment_v2(aid, page = 1, order = 0): 173 | """ 174 | 输入: 175 | aid:AV号 176 | page:页码 177 | order:排序方式 默认按发布时间倒序 可选:1 按热门排序 2 按点赞数排序 178 | 返回: 179 | 评论列表""" 180 | url = "http://api.bilibili.com/x/reply?type=1&oid=%s&pn=%s&nohot=1&sort=%s"%(GetString(aid),GetString(page),GetString(order)) 181 | jsoninfo = JsonInfo(url) 182 | commentList = CommentList() 183 | commentList.comments = [] 184 | commentList.commentLen = jsoninfo.Getvalue('data','page','count') 185 | for comment in jsoninfo.Getvalue('data','replies'): 186 | liuyan = Comment() 187 | liuyan.lv = comment['floor'] 188 | liuyan.fbid = comment['rpid'] 189 | liuyan.parent_id = comment['parent'] 190 | liuyan.like = comment['like'] 191 | liuyan.msg = comment['content']['message'] 192 | liuyan.post_user = User(comment['member']['mid'],comment['member']['uname']) 193 | liuyan.post_user.avatar = comment['member']['avatar'] 194 | liuyan.post_user.rank = comment['member']['rank'] 195 | commentList.comments.append(liuyan) 196 | return commentList 197 | 198 | 199 | def GetAllComment(aid, order = None): 200 | """ 201 | 获取一个视频全部评论,有可能需要多次爬取,所以会有较大耗时 202 | 输入: 203 | aid:AV号 204 | order:排序方式 默认按发布时间倒序 可选:good 按点赞人数排序 hot 按热门回复排序 205 | 返回: 206 | 评论列表 207 | """ 208 | MaxPageSize = 300 209 | commentList = GetComment(aid=aid, pagesize=MaxPageSize, order=order) 210 | if commentList.page == 1: 211 | return commentList 212 | for p in range(2,commentList.page+1): 213 | t_commentlist = GetComment(aid=aid,pagesize=MaxPageSize,page=p,order=order) 214 | for liuyan in t_commentlist.comments: 215 | commentList.comments.append(liuyan) 216 | time.sleep(0.5) 217 | return commentList 218 | 219 | def GetVideoInfo(aid, appkey,page = 1, AppSecret=None, fav = None): 220 | """ 221 | 获取视频信息 222 | 输入: 223 | aid:AV号 224 | page:页码 225 | fav:是否读取会员收藏状态 (默认 0) 226 | """ 227 | paras = {'id': GetString(aid),'page': GetString(page)} 228 | if fav: 229 | paras['fav'] = fav 230 | url = 'http://api.bilibili.cn/view?'+GetSign(paras,appkey,AppSecret) 231 | jsoninfo = JsonInfo(url) 232 | if jsoninfo.error: 233 | print jsoninfo.ERROR_MSG 234 | return None 235 | video = Video(aid,jsoninfo.Getvalue('title')) 236 | video.guankan = jsoninfo.Getvalue('play') 237 | video.commentNumber = jsoninfo.Getvalue('review') 238 | video.danmu = jsoninfo.Getvalue('video_review') 239 | video.shoucang = jsoninfo.Getvalue('favorites') 240 | video.description = jsoninfo.Getvalue('description') 241 | video.tag = [] 242 | taglist = jsoninfo.Getvalue('tag') 243 | if taglist: 244 | for tag in taglist.split(','): 245 | video.tag.append(tag) 246 | video.cover = jsoninfo.Getvalue('pic') 247 | video.author = User(jsoninfo.Getvalue('mid'),jsoninfo.Getvalue('author')) 248 | video.page = jsoninfo.Getvalue('pages') 249 | video.date = jsoninfo.Getvalue('created_at') 250 | video.credit = jsoninfo.Getvalue('credit') 251 | video.coin = jsoninfo.Getvalue('coins') 252 | video.spid = jsoninfo.Getvalue('spid') 253 | video.cid = jsoninfo.Getvalue('cid') 254 | video.offsite = jsoninfo.Getvalue('offsite') 255 | video.partname = jsoninfo.Getvalue('partname') 256 | video.src = jsoninfo.Getvalue('src') 257 | video.tid = jsoninfo.Getvalue('tid') 258 | video.typename = jsoninfo.Getvalue('typename') 259 | video.instant_server = jsoninfo.Getvalue('instant_server') 260 | ## 以下三个意义不明。。 261 | # video.allow_bp = jsoninfo.Getvalue('allow_bp') 262 | # video.allow_feed = jsoninfo.Getvalue('allow_feed') 263 | # video.created = jsoninfo.Getvalue('created') 264 | return video 265 | 266 | 267 | def GetBangumi(appkey, btype = None, weekday = None, AppSecret=None): 268 | """ 269 | 获取新番信息 270 | 输入: 271 | btype:番剧类型 2: 二次元新番 3: 三次元新番 默认(0):所有 272 | weekday:周一:1 周二:2 ...周六:6 273 | """ 274 | paras = {} 275 | if btype != None and btype in [2,3]: 276 | paras['btype'] = GetString(btype) 277 | if weekday != None: 278 | paras['weekday'] = GetString(weekday) 279 | url = 'http://api.bilibili.cn/bangumi?' + GetSign(paras, appkey, AppSecret) 280 | jsoninfo = JsonInfo(url) 281 | bangumilist = [] 282 | if jsoninfo.error: 283 | print jsoninfo.ERROR_MSG 284 | return bangumilist 285 | for bgm in jsoninfo.Getvalue('list'): 286 | bangumi = Bangumi() 287 | bgm = DictDecode2UTF8(bgm) 288 | bangumi.typeid = bgm['typeid'] 289 | bangumi.lastupdate = bgm['lastupdate'] 290 | bangumi.areaid = bgm['areaid'] 291 | bangumi.bgmcount = getint(bgm['bgmcount']) 292 | bangumi.title = bgm['title'] 293 | bangumi.lastupdate_at = bgm['lastupdate_at'] 294 | bangumi.attention = bgm['attention'] 295 | bangumi.cover = bgm['cover'] 296 | bangumi.priority = bgm['priority'] 297 | bangumi.area = bgm['area'] 298 | bangumi.weekday = bgm['weekday'] 299 | bangumi.spid = bgm['spid'] 300 | bangumi.new = bgm['new'] 301 | bangumi.scover = bgm['scover'] 302 | bangumi.mcover = bgm['mcover'] 303 | bangumi.click = bgm['click'] 304 | bangumi.season_id = bgm['season_id'] 305 | bangumi.click = bgm['click'] 306 | bangumi.video_view = bgm['video_view'] 307 | bangumilist.append(bangumi) 308 | return bangumilist 309 | 310 | def biliVideoSearch(appkey, AppSecret, keyword, order = 'default', pagesize = 20, page = 1): 311 | """ 312 | 【注】: 313 | 旧版Appkey不可用,必须配合AppSecret使用!! 314 | 315 | 根据关键词搜索视频 316 | 输入: 317 | order:排序方式 默认default,其余待测试 318 | keyword:关键词 319 | pagesize:返回条目多少 320 | page:页码 321 | """ 322 | paras = {} 323 | paras['keyword'] = GetString(keyword) 324 | paras['order'] = GetString(order) 325 | paras['pagesize'] = GetString(pagesize) 326 | paras['page'] = GetString(page) 327 | url = 'http://api.bilibili.cn/search?' + GetSign(paras, appkey, AppSecret) 328 | jsoninfo = JsonInfo(url) 329 | videolist = [] 330 | for video_idx in jsoninfo.Getvalue('result'): 331 | if video_idx['type'] != 'video': 332 | continue 333 | video_idx = DictDecode2UTF8(video_idx) 334 | video = Video(video_idx['aid'], video_idx['title']) 335 | video.typename = video_idx['typename'] 336 | video.author = User(video_idx['mid'], video_idx['author']) 337 | video.acurl = video_idx['arcurl'] 338 | video.description = video_idx['description'] 339 | video.arcrank = video_idx['arcrank'] 340 | video.cover = video_idx['pic'] 341 | video.guankan = video_idx['play'] 342 | video.danmu = video_idx['video_review'] 343 | video.shoucang = video_idx['favorites'] 344 | video.commentNumber = video_idx['review'] 345 | video.date = video_idx['pubdate'] 346 | video.tag = video_idx['tag'].split(',') 347 | videolist.append(video) 348 | return videolist 349 | 350 | def biliZhuantiSearch(appkey, AppSecret, keyword): 351 | """ 352 | 【注】: 353 | 旧版Appkey不可用,必须配合AppSecret使用!! 354 | 根据关键词搜索专题 355 | 输入: 356 | keyword:关键词 357 | """ 358 | paras = {} 359 | paras['keyword'] = GetString(keyword) 360 | url = 'http://api.bilibili.cn/search?' + GetSign(paras, appkey, AppSecret) 361 | jsoninfo = JsonInfo(url) 362 | zhuantiList = [] 363 | for zhuanti_idx in jsoninfo.Getvalue('result'): 364 | if zhuanti_idx['type'] != 'special': 365 | continue 366 | zhuanti_idx = DictDecode2UTF8(zhuanti_idx) 367 | zhuanti = ZhuantiInfo(zhuanti_idx['spid'], zhuanti_idx['title']) 368 | zhuanti.author = User(zhuanti_idx['mid'], zhuanti_idx['author']) 369 | zhuanti.cover = zhuanti_idx['pic'] 370 | zhuanti.thumb = zhuanti_idx['thumb'] 371 | zhuanti.ischeck = zhuanti_idx['ischeck'] 372 | zhuanti.tag = zhuanti_idx['tag'].split(',') 373 | zhuanti.description = zhuanti_idx['description'] 374 | zhuanti.pubdate = zhuanti_idx['pubdate'] 375 | zhuanti.postdate = zhuanti_idx['postdate'] 376 | zhuanti.lastupdate = zhuanti_idx['lastupdate'] 377 | zhuanti.click = zhuanti_idx['click'] 378 | zhuanti.favourite = zhuanti_idx['favourite'] 379 | zhuanti.attention = zhuanti_idx['attention'] 380 | zhuanti.count = zhuanti_idx['count'] 381 | zhuanti.bgmcount = zhuanti_idx['bgmcount'] 382 | zhuanti.spcount = zhuanti_idx['spcount'] 383 | zhuanti.season_id = zhuanti_idx['season_id'] 384 | zhuanti.is_bangumi = zhuanti_idx['is_bangumi'] 385 | zhuanti.arcurl = zhuanti_idx['arcurl'] 386 | zhuanti.is_bangumi_end = zhuanti_idx['is_bangumi_end'] 387 | zhuantiList.append(zhuanti) 388 | return zhuantiList 389 | 390 | #def GetBangumiByTime(year, month): 391 | # url='http://www.bilibili.tv/index/bangumi/%s-%s.json'%(GetString(year),GetString(month)) 392 | # print url 393 | # jsoninfo = getURLContent(url) 394 | # print jsoninfo 395 | 396 | def GetRank(appkey, tid=None, begin=None, end=None, page = None, pagesize=None, click_detail =None, order = None, AppSecret=None): 397 | """ 398 | 获取排行信息 399 | 输入: 400 | 详见https://github.com/Vespa314/bilibili-api/blob/master/api.md 401 | 输出: 402 | 详见https://github.com/Vespa314/bilibili-api/blob/master/api.md 403 | 备注: 404 | pagesize ≤ 100 405 | """ 406 | paras = {} 407 | paras['appkey']=appkey 408 | if tid: 409 | paras['tid']=GetString(tid) 410 | if order: 411 | paras['order']=order 412 | if click_detail: 413 | paras['click_detail']=click_detail 414 | if pagesize: 415 | paras['pagesize']=GetString(pagesize) 416 | if begin != None and len(begin)==3: 417 | paras['begin']='%d-%d-%d'%(begin[0],begin[1],begin[2]) 418 | if end != None and len(end)==3: 419 | paras['end']='%d-%d-%d'%(end[0],end[1],end[2]) 420 | if page: 421 | paras['page']=GetString(page) 422 | if click_detail: 423 | paras['click_detail'] = click_detail 424 | url = 'http://api.bilibili.cn/list?' + GetSign(paras,appkey,AppSecret) 425 | jsoninfo = JsonInfo(url) 426 | videolist = [] 427 | if jsoninfo.error: 428 | print jsoninfo.ERROR_MSG 429 | return [-1,"None",videolist] 430 | total_page = jsoninfo.Getvalue('pages') 431 | name = jsoninfo.Getvalue('name') 432 | for i in range(len(jsoninfo.Getvalue('list'))-1): 433 | idx = str(i) 434 | video = Video(jsoninfo.Getvalue('list',idx,'aid'),jsoninfo.Getvalue('list',idx,'title')) 435 | video.Iscopy = jsoninfo.Getvalue('list',idx,'copyright') 436 | video.tid = jsoninfo.Getvalue('list',idx,'typeid') 437 | video.typename = jsoninfo.Getvalue('list',idx,'typename') 438 | video.subtitle = jsoninfo.Getvalue('list',idx,'subtitle') 439 | video.guankan = jsoninfo.Getvalue('list',idx,'play') 440 | # video.commentNumber = jsoninfo.Getvalue('list',idx,'review') 441 | video.danmu = jsoninfo.Getvalue('list',idx,'video_review') 442 | video.shoucang = jsoninfo.Getvalue('list',idx,'favorites') 443 | video.author = User(jsoninfo.Getvalue('list',idx,'mid'),jsoninfo.Getvalue('list',idx,'author')) 444 | video.description = jsoninfo.Getvalue('list',idx,'description') 445 | video.date = jsoninfo.Getvalue('list',idx,'create') 446 | video.cover = jsoninfo.Getvalue('list',idx,'pic') 447 | video.credit = jsoninfo.Getvalue('list',idx,'credit') 448 | video.coin = jsoninfo.Getvalue('list',idx,'coins') 449 | video.commentNumber = jsoninfo.Getvalue('list',idx,'comment') 450 | video.duration = jsoninfo.Getvalue('list',idx,'duration') 451 | if click_detail != None: 452 | video.play_site = jsoninfo.Getvalue('list',idx,'play_site') 453 | video.play_forward = jsoninfo.Getvalue('list',idx,'play_forward') 454 | video.play_mobile = jsoninfo.Getvalue('list',idx,'play_mobile') 455 | videolist.append(video) 456 | return [total_page,name,videolist] 457 | 458 | def GetDanmukuContent(cid): 459 | """ 460 | 获取弹幕内容 461 | """ 462 | content = GetRE(GetDanmuku(cid),r']*>([^<]*)<') 463 | return content 464 | 465 | def GetDanmuku(cid): 466 | """ 467 | 获取弹幕xml内容 468 | """ 469 | cid = getint(cid) 470 | url = "http://comment.bilibili.cn/%d.xml"%(cid) 471 | content = zlib.decompressobj(-zlib.MAX_WBITS).decompress(getURLContent(url)) 472 | return content 473 | 474 | def ParseDanmuku(cid): 475 | """ 476 | 按时间顺序返回每一条弹幕 477 | """ 478 | Danmuku = [] 479 | DanmukuContent = GetDanmuku(cid) 480 | if DanmukuContent == "": 481 | return None 482 | Danmuku.extend(ParseComment(DanmukuContent)) 483 | Danmuku.sort(key=lambda x:x.t_video) 484 | return Danmuku 485 | 486 | def Danmaku2ASS(input_files, output_file, stage_width, stage_height, reserve_blank=0, font_face='sans-serif', font_size=25.0, text_opacity=1.0, comment_duration=5.0, is_reduce_comments=False, progress_callback=None): 487 | """ 488 | 获取弹幕转化成ass文件 489 | input_files:弹幕文件,可由GetDanmuku(cid)获得 490 | output_file:输出ASS文件路径 491 | """ 492 | fo = None 493 | comments = ReadComments(input_files, font_size) 494 | try: 495 | fo = ConvertToFile(output_file, 'w') 496 | ProcessComments(comments, fo, stage_width, stage_height, reserve_blank, font_face, font_size, text_opacity, comment_duration, is_reduce_comments, progress_callback) 497 | finally: 498 | if output_file and fo != output_file: 499 | fo.close() 500 | 501 | def GetBilibiliUrl(url, appkey, AppSecret=None): 502 | overseas=False 503 | url_get_media = 'http://interface.bilibili.com/playurl?' if not overseas else 'http://interface.bilibili.com/v_cdn_play?' 504 | regex_match = re.findall('http:/*[^/]+/video/av(\\d+)(/|/index.html|/index_(\\d+).html)?(\\?|#|$)',url) 505 | if not regex_match: 506 | raise ValueError('Invalid URL: %s' % url) 507 | aid = regex_match[0][0] 508 | pid = regex_match[0][2] or '1' 509 | video = GetVideoInfo(aid,appkey,pid,AppSecret) 510 | cid = video.cid 511 | media_args = {'otype': 'json', 'cid': cid, 'type': 'mp4', 'quality': 4} 512 | resp_media = getURLContent(url_get_media+GetSign(media_args,appkey,AppSecret)) 513 | resp_media = dict(json.loads(resp_media.decode('utf-8', 'replace'))) 514 | res = [] 515 | for media_url in resp_media.get('durl'): 516 | res.append(media_url.get('url')) 517 | return res 518 | 519 | def GetVideoOfUploader(mid,pagesize=20,page=1): 520 | url = 'http://space.bilibili.com/ajax/member/getSubmitVideos?mid=%d&pagesize=%d&page=%d'%(getint(mid),getint(pagesize),getint(page)) 521 | jsoninfo = JsonInfo(url) 522 | videolist = [] 523 | for video_t in jsoninfo.Getvalue('data','vlist'): 524 | video = Video(video_t['aid'],video_t['title']) 525 | video.Iscopy = video_t['copyright'] 526 | video.tid = video_t['typeid'] 527 | video.subtitle = video_t['subtitle'] 528 | video.guankan = video_t['play'] 529 | video.commentNumber = video_t['review'] 530 | video.shoucang = video_t['favorites'] 531 | video.author = User(video_t['mid'],video_t['author']) 532 | video.description = video_t['description'] 533 | video.date = video_t['created'] 534 | video.cover = video_t['pic'] 535 | video.duration = video_t['length'] 536 | video.danmu = video_t['comment'] 537 | videolist.append(video) 538 | return videolist 539 | 540 | def GetSponsorInfo(aid,page=None): 541 | """ 542 | 获取剧番承包信息,每页返回25个承保用户,总页数自己算 543 | """ 544 | url = 'http://www.bilibili.com/widget/ajaxGetBP?aid=%s'%(GetString(aid)) 545 | if page: 546 | url += '&page='+GetString(page) 547 | jsoninfo = JsonInfo(url) 548 | sponsorinfo = SponsorInfo() 549 | sponsorinfo.bp = jsoninfo.Getvalue('bp') 550 | sponsorinfo.percent = jsoninfo.Getvalue('percent') 551 | sponsorinfo.ep_bp = jsoninfo.Getvalue('ep_bp') 552 | sponsorinfo.ep_percent = jsoninfo.Getvalue('ep_percent') 553 | sponsorinfo.sponsor_num = jsoninfo.Getvalue('users') 554 | sponsorinfo.sponsor_user = [] 555 | for tuhao_user in jsoninfo.Getvalue('list','list'): 556 | user = User(tuhao_user['uid'],tuhao_user['uname']) 557 | user.rank = tuhao_user['rank'] 558 | if tuhao_user.has_key('face'): 559 | user.avatar = tuhao_user['face'] 560 | user.message = tuhao_user['message'] 561 | sponsorinfo.sponsor_user.append(user) 562 | return sponsorinfo 563 | 564 | def HasLiving(mid): 565 | """ 566 | 用户是否开通直播,如果有,返回房间号 567 | """ 568 | url = "http://space.bilibili.com/ajax/live/getLive?mid=%s"%(GetString(mid)) 569 | jsoninfo = JsonInfo(url) 570 | if jsoninfo.Getvalue('status') == True: 571 | return jsoninfo.Getvalue('data') 572 | else: 573 | return None 574 | 575 | def IsLiving(mid): 576 | """ 577 | 是否在直播 578 | """ 579 | url = "http://live.bilibili.com/bili/isliving/%s"%(GetString(mid)) 580 | jsoninfo = JsonInfo(url,pre_deal=lambda x:x[1:-2]) 581 | info = LivingInfo() 582 | if jsoninfo.Getvalue('data'): 583 | info.url = jsoninfo.Getvalue('data','url') 584 | info.title = jsoninfo.Getvalue('data','title') 585 | info.cover = jsoninfo.Getvalue('data','cover') 586 | info.mid = mid 587 | return info 588 | else: 589 | return None 590 | 591 | def GetOnlineUser(): 592 | url = "http://www.bilibili.com/online.js" 593 | content = getURLContent(url) 594 | web_online = GetRE(content,r'web_online = (\d+)') 595 | play_online = GetRE(content,r'play_online = (\d+)') 596 | return int(web_online[0]),int(play_online[0]) 597 | 598 | def GetOnloneTopVideo(): 599 | """ 600 | 获取在线人数最多的视频,随时失效。。 601 | """ 602 | url = "http://www.bilibili.com/video/online.html" 603 | content = getURLContent(url) 604 | regexp = r'

\3

(\d+)(\d+)(((?!class).)*)

(\d+)在线

' 605 | result = GetRE(content,regexp) 606 | videolist = [] 607 | for res in result: 608 | video = Video(res[1],res[2]) 609 | video.tid = int(res[0]) 610 | video.cover = res[3] 611 | video.guankan = int(res[4]) 612 | video.danmu = int(res[5]) 613 | video.author = User(None,res[6]) 614 | video.online_user = int(res[8]) 615 | videolist.append(video) 616 | return videolist 617 | 618 | def GetVideoOfBangumi_V2(id): 619 | """ 620 | 非API版本获取剧番aid和视频名,解决GetVideoOfZhuanti API一些SPID找不到视频的问题。 621 | 传入id为剧番URL中的id:如http://www.bilibili.com/bangumi/i/2894/中的2894 622 | 由于是解析html,所以只获取aid和title 623 | 只适用于新番,不适合其他专题 624 | """ 625 | url = "http://www.bilibili.com/bangumi/i/%d"%(getint(id)) 626 | content = getURLContent(url) 627 | regexp = r'
[^<]+' 628 | result = GetRE(content,regexp) 629 | videolist = [] 630 | for aid,title in result: 631 | videolist.append(Video(aid,title)) 632 | return videolist 633 | 634 | 635 | if __name__ == "__main__": 636 | xmlid = '8581509' 637 | Desktop_Path = '%s/Desktop/'%(os.path.expanduser('~')) 638 | fid = open('%s%s.xml'%(Desktop_Path,xmlid)) 639 | Danmaku2ASS(fid.read(),r'%s%s.ass'%(Desktop_Path,xmlid), 640, 360, 0, 'sans-serif', 15, 0.5, 10, False) -------------------------------------------------------------------------------- /xml2ass/source/convert.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Mon May 26 23:42:03 2014 4 | 5 | @author: Vespa 6 | """ 7 | 8 | from support import * 9 | def Danmaku2ASS(input_files, output_file, stage_width, stage_height, reserve_blank=0, font_face='sans-serif', font_size=25.0, text_opacity=1.0, comment_duration=5.0, is_reduce_comments=False, progress_callback=None): 10 | """ 11 | 获取弹幕转化成ass文件 12 | input_files:弹幕文件,可由GetDanmuku(cid)获得 13 | output_file:输出ASS文件路径 14 | """ 15 | fo = None 16 | comments = ReadComments(input_files, font_size) 17 | try: 18 | fo = ConvertToFile(output_file, 'w') 19 | ProcessComments(comments, fo, stage_width, stage_height, reserve_blank, font_face, font_size, text_opacity, comment_duration, is_reduce_comments, progress_callback) 20 | finally: 21 | if output_file and fo != output_file: 22 | fo.close() 23 | 24 | 25 | if __name__ == "__main__": 26 | xmlid = "{query}" 27 | Desktop_Path = '%s/Desktop/'%(os.path.expanduser('~')) 28 | fid = open('%s%s.xml'%(Desktop_Path,xmlid)) 29 | Danmaku2ASS(fid.read(),r'%s%s.ass'%(Desktop_Path,xmlid), 640, 360, 0, 'sans-serif', 15, 0.5, 10, False) -------------------------------------------------------------------------------- /xml2ass/source/support.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Mon May 26 23:59:09 2014 4 | 5 | @author: Vespa 6 | """ 7 | import urllib2 8 | import urllib 9 | import re 10 | import json 11 | import zlib 12 | import gzip 13 | import xml.dom.minidom 14 | import hashlib 15 | from biclass import * 16 | import time 17 | import sys 18 | import os 19 | from GetAssDanmaku import * 20 | def GetRE(content,regexp): 21 | return re.findall(regexp, content) 22 | 23 | def getURLContent(url): 24 | while True: 25 | flag = 1 26 | try: 27 | headers = {'User-Agent':'Mozilla/5.0 (Windows U Windows NT 6.1 en-US rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6'} 28 | req = urllib2.Request(url = url,headers = headers) 29 | page = urllib2.urlopen(req) 30 | content = page.read() 31 | except urllib2.HTTPError,e: 32 | if e.code == 404: 33 | return "" 34 | flag = 0 35 | time.sleep(5) 36 | if flag == 1: 37 | break 38 | if page.info().get('Content-Encoding') == 'gzip': 39 | content = zlib.decompress(content, 16+zlib.MAX_WBITS) 40 | return content 41 | 42 | class JsonInfo(): 43 | def __init__(self,url,pre_deal=lambda x:x): 44 | self.info = json.loads(pre_deal(getURLContent(url))) 45 | if self.info.has_key('code') and self.info['code'] != 0: 46 | if self.info.has_key('message'): 47 | print "【Error】code=%d, msg=%s, url=%s"%(self.info['code'],self.Getvalue('message'),url) 48 | self.ERROR_MSG = self.Getvalue('message') 49 | elif self.info.has_key('error'): 50 | print "【Error】code=%d, msg=%s, url=%s"%(self.info['code'],self.Getvalue('error'),url) 51 | self.ERROR_MSG = self.Getvalue('error') 52 | self.error = True 53 | def Getvalue(self,*keys): 54 | if len(keys) == 0: 55 | return None 56 | if self.info.has_key(keys[0]): 57 | temp = self.info[keys[0]] 58 | else: 59 | return None 60 | if len(keys) > 1: 61 | for key in keys[1:]: 62 | if type(temp) == dict and temp.has_key(key): 63 | temp = temp[key] 64 | else: 65 | return None 66 | if isinstance(temp,unicode): 67 | temp = temp.encode('utf8') 68 | return temp 69 | info = None 70 | error = False 71 | ERROR_MSG = "" 72 | 73 | def GetString(t): 74 | if type(t) == int: 75 | return str(t) 76 | return t 77 | 78 | def getint(string): 79 | try: 80 | i = int(string) 81 | except: 82 | i = 0 83 | return i 84 | 85 | def DictDecode2UTF8(dict): 86 | for keys in dict: 87 | if isinstance(dict[keys],unicode): 88 | dict[keys] = dict[keys].encode('utf8') 89 | return dict 90 | 91 | def GetVideoFromRate(content): 92 | """ 93 | 从视频搜索源码页面提取视频信息 94 | """ 95 | #av号和标题 96 | regular1 = r']*>(.*)' 97 | info1 = GetRE(content,regular1) 98 | #观看数 99 | regular2 = r'\1' 100 | info2 = GetRE(content,regular2) 101 | #收藏 102 | regular3 = r'\1' 103 | info3 = GetRE(content,regular3) 104 | #弹幕 105 | regular4 = r'\1' 106 | info4 = GetRE(content,regular4) 107 | #日期 108 | regular5 = r'(.+)' 109 | info5 = GetRE(content,regular5) 110 | #封面 111 | regular6 = r']*>' 112 | info6 = GetRE(content,regular6) 113 | #Up的id和名字 114 | regular7 = r'(.+)' 115 | info7 = GetRE(content,regular7) 116 | #!!!!!!!!这里可以断言所有信息长度相等 117 | videoNum = len(info1)#视频长度 118 | videoList = [] 119 | 120 | for i in range(videoNum): 121 | video_t = Video() 122 | video_t.aid = getint(info1[i][0]) 123 | video_t.title = info1[i][1] 124 | video_t.guankan = getint(info2[i]) 125 | video_t.shoucang = getint(info3[i]) 126 | video_t.danmu = getint(info4[i]) 127 | video_t.date = info5[i] 128 | video_t.cover = info6[i] 129 | video_t.author = User(info7[i][0],info7[i][1]) 130 | videoList.append(video_t) 131 | return videoList 132 | 133 | def GetSign(params, appkey, AppSecret=None): 134 | """ 135 | 获取新版API的签名,不然会返回-3错误 136 | """ 137 | params['appkey']=appkey 138 | data = "" 139 | paras = params.keys() 140 | paras.sort() 141 | for para in paras: 142 | if data != "": 143 | data += "&" 144 | data += para + "=" + str(urllib.quote(GetString(params[para]))) 145 | if AppSecret == None: 146 | return data 147 | m = hashlib.md5() 148 | m.update(data+AppSecret) 149 | return data+'&sign='+m.hexdigest() 150 | 151 | def ParseComment(danmu): 152 | dom = xml.dom.minidom.parseString(danmu) 153 | comment_element = dom.getElementsByTagName('d') 154 | for i, comment in enumerate(comment_element): 155 | p = str(comment.getAttribute('p')).split(',') 156 | danmu = Danmu() 157 | danmu.t_video = float(p[0]) 158 | danmu.danmu_type = int(p[1]) 159 | danmu.t_stamp = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(float(p[4]))) 160 | danmu.mid_crc = p[6] 161 | danmu.danmu_color = ConvertColor(int(p[3])) 162 | danmu_fontsize = int(p[2]) 163 | if len(comment.childNodes) != 0: 164 | danmu.content = str(comment.childNodes[0].wholeText).replace('/n', '\n') 165 | else: 166 | danmu.content = "" 167 | yield danmu -------------------------------------------------------------------------------- /xml2ass/source/xml2ass.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Mon May 26 23:42:03 2014 4 | 5 | @author: Vespa 6 | """ 7 | 8 | import os 9 | 10 | from Feedback import * 11 | 12 | fb = Feedback() 13 | 14 | Desktop_Path = '%s/Desktop/'%(os.path.expanduser('~')) 15 | list_dirs = os.listdir(Desktop_Path) 16 | for line in list_dirs: 17 | filepath = os.path.join(Desktop_Path,line) 18 | if not os.path.isdir(filepath): 19 | if line.endswith('xml'): 20 | xmlid = line.split('.')[0] 21 | fb.add_item(xmlid+'.xml',subtitle='转化'+xmlid+'.xml',arg=xmlid) 22 | 23 | print fb -------------------------------------------------------------------------------- /xml2ass/xml2ass.alfredworkflow: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vespa314/BilibiliAlfredWorkFlows/43f3a27d35bd28118bf9935d4236344b2d5d86d7/xml2ass/xml2ass.alfredworkflow --------------------------------------------------------------------------------