增加爱奇艺链接方式解析

This commit is contained in:
xhlove
2020-01-05 14:49:33 +08:00
parent 858fb65ede
commit 78083c2295
11 changed files with 239 additions and 72 deletions

View File

@@ -1,9 +1,9 @@
#!/usr/bin/env python #!/usr/bin/env python3.7
# coding=utf-8 # coding=utf-8
''' '''
# 作者: weimo # 作者: weimo
# 创建日期: 2020-01-04 12:59:11 # 创建日期: 2020-01-04 19:14:39
# 上次编辑时间 : 2020-01-04 20:14:39 # 上次编辑时间: 2020-01-05 14:47:53
# 一个人的命运啊,当然要靠自我奋斗,但是... # 一个人的命运啊,当然要靠自我奋斗,但是...
''' '''
@@ -13,10 +13,7 @@ from argparse import ArgumentParser
from sites.qq import main as qq from sites.qq import main as qq
from sites.iqiyi import main as iqiyi from sites.iqiyi import main as iqiyi
from basic.ass import get_ass_head, check_font from pfunc.cfunc import check_url_site
from pfunc.dump_to_ass import write_lines_to_file
from methods.assbase import ASS
from methods.sameheight import SameHeight
# ------------------------------------------- # -------------------------------------------
# 基本流程 # 基本流程
@@ -32,7 +29,7 @@ def main():
parser = ArgumentParser(description="视频网站弹幕转换/下载工具任何问题请联系vvtoolbox.dev@gmail.com") parser = ArgumentParser(description="视频网站弹幕转换/下载工具任何问题请联系vvtoolbox.dev@gmail.com")
parser.add_argument("-f", "--font", default="微软雅黑", help="指定输出字幕字体") parser.add_argument("-f", "--font", default="微软雅黑", help="指定输出字幕字体")
parser.add_argument("-fs", "--font-size", default=28, help="指定输出字幕字体大小") parser.add_argument("-fs", "--font-size", default=28, help="指定输出字幕字体大小")
parser.add_argument("-s", "--site", default="qq", help="指定网站") parser.add_argument("-s", "--site", default="", help="指定网站")
parser.add_argument("-cid", "--cid", default="", help="下载cid对应视频的弹幕腾讯视频合集") parser.add_argument("-cid", "--cid", default="", help="下载cid对应视频的弹幕腾讯视频合集")
parser.add_argument("-vid", "--vid", default="", help="下载vid对应视频的弹幕支持同时多个vid需要用逗号隔开") parser.add_argument("-vid", "--vid", default="", help="下载vid对应视频的弹幕支持同时多个vid需要用逗号隔开")
parser.add_argument("-aid", "--aid", default="", help="下载aid对应视频的弹幕爱奇艺合集") parser.add_argument("-aid", "--aid", default="", help="下载aid对应视频的弹幕爱奇艺合集")
@@ -41,20 +38,15 @@ def main():
parser.add_argument("-y", "--y", action="store_true", help="覆盖原有弹幕而不提示") parser.add_argument("-y", "--y", action="store_true", help="覆盖原有弹幕而不提示")
args = parser.parse_args() args = parser.parse_args()
# print(args.__dict__) # print(args.__dict__)
font_path, font_style_name = check_font(args.font) if args.url != "":
ass_head = get_ass_head(font_style_name, args.font_size) args.site = check_url_site(args.url)
if args.site == "":
args.site = input("请输入站点qq/iqiyi/youku\n")
if args.site == "qq": if args.site == "qq":
subtitles = qq(args) subtitles = qq(args)
if args.site == "iqiyi": if args.site == "iqiyi":
subtitles = iqiyi(args) subtitles = iqiyi(args)
for file_path, comments in subtitles.items():
get_xy_obj = SameHeight("那就写这一句作为初始化测试吧!", font_path=font_path, font_size=args.font_size)
subtitle = ASS(file_path, get_xy_obj, font=font_style_name)
for comment in comments:
subtitle.create_new_line(comment)
write_lines_to_file(ass_head, subtitle.lines, file_path)
if __name__ == "__main__": if __name__ == "__main__":
# 打包 --> pyinstaller GetDanMu.spec # 打包 --> pyinstaller GetDanMu.spec
main() main()

View File

@@ -1,2 +1,30 @@
<!--
* @作者: weimo
* @创建日期: 2020-01-04 18:45:58
* @上次编辑时间 : 2020-01-05 14:42:42
* @一个人的命运啊,当然要靠自我奋斗,但是...
-->
# GetDanMu # GetDanMu
转换/下载各类视频的弹幕 转换/下载各类视频的弹幕
## 网站支持
| Site | URL | 单集? | 合集? | 综艺合集? |
| :--: | :-- | :-----: | :-----: | :-----: |
| **腾讯视频** | <https://v.qq.com/> |✓|✓| |
| **爱奇艺** | <https://www.iqiyi.com/> |✓|✓|✓|
## 可能存在的问题
- 下载进度接近100%时暂时没有反应
这是因为在全部弹幕获取完后一次性处理所致,对于时间过长和弹幕过多的视频,处理耗时较多,属于正常现象。
- 命令组合未达到预期效果
当前的逻辑并不完善,如果出现这种现象请反馈给我。
# 更新日志
## 2020/1/5
- 增加了通过链接下载爱奇艺视频弹幕的方法,支持综艺合集。
- 增加通过链接判断网站

View File

@@ -1,9 +1,9 @@
#!/usr/bin/env python #!/usr/bin/env python3.7
# coding=utf-8 # coding=utf-8
''' '''
# 作者: weimo # 作者: weimo
# 创建日期: 2020-01-04 13:05:23 # 创建日期: 2020-01-04 19:14:46
# 上次编辑时间 : 2020-01-04 15:52:11 # 上次编辑时间: 2020-01-05 14:45:55
# 一个人的命运啊,当然要靠自我奋斗,但是... # 一个人的命运啊,当然要靠自我奋斗,但是...
''' '''

View File

@@ -1,9 +1,9 @@
#!/usr/bin/env python #!/usr/bin/env python3.7
# coding=utf-8 # coding=utf-8
''' '''
# 作者: weimo # 作者: weimo
# 创建日期: 2020-01-04 13:16:18 # 创建日期: 2020-01-04 19:14:35
# 上次编辑时间 : 2020-01-04 16:08:34 # 上次编辑时间: 2020-01-05 14:46:15
# 一个人的命运啊,当然要靠自我奋斗,但是... # 一个人的命运啊,当然要靠自我奋斗,但是...
''' '''
qqlive = { qqlive = {
@@ -12,6 +12,9 @@ qqlive = {
iqiyiplayer = { iqiyiplayer = {
"User-Agent":"Qiyi List Client PC 7.2.102.1343" "User-Agent":"Qiyi List Client PC 7.2.102.1343"
} }
chrome = {
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36"
}
fonts = { fonts = {
"微软雅黑":"msyh.ttc", "微软雅黑":"msyh.ttc",
"微软雅黑粗体":"msyhbd.ttc", "微软雅黑粗体":"msyhbd.ttc",

View File

@@ -1,9 +1,9 @@
#!/usr/bin/env python #!/usr/bin/env python3.7
# coding=utf-8 # coding=utf-8
''' '''
# 作者: weimo # 作者: weimo
# 创建日期: 2020-01-04 13:01:04 # 创建日期: 2020-01-04 19:14:32
# 上次编辑时间 : 2020-01-04 15:42:02 # 上次编辑时间: 2020-01-05 14:46:27
# 一个人的命运啊,当然要靠自我奋斗,但是... # 一个人的命运啊,当然要靠自我奋斗,但是...
''' '''

View File

@@ -1,12 +1,13 @@
#!/usr/bin/env python #!/usr/bin/env python3.7
# coding=utf-8 # coding=utf-8
''' '''
# 作者: weimo # 作者: weimo
# 创建日期: 2019-12-25 20:35:43 # 创建日期: 2020-01-04 19:14:47
# 上次编辑时间 : 2019-12-25 23:23:32 # 上次编辑时间: 2020-01-05 14:46:51
# 一个人的命运啊,当然要靠自我奋斗,但是... # 一个人的命运啊,当然要靠自我奋斗,但是...
''' '''
from PIL.ImageFont import truetype from PIL.ImageFont import truetype
class SameHeight(object): class SameHeight(object):

24
pfunc/cfunc.py Normal file
View File

@@ -0,0 +1,24 @@
#!/usr/bin/env python3.7
# coding=utf-8
'''
# 作者: weimo
# 创建日期: 2020-01-05 12:45:18
# 上次编辑时间: 2020-01-05 14:44:42
# 一个人的命运啊,当然要靠自我奋斗,但是...
'''
from urllib.parse import urlparse
def check_url_site(url):
return urlparse(url).netloc.split(".")[-2]
def check_url_locale(url):
flag = {
"cn":"zh_cn",
"tw":"zh_tw",
"intl":"intl"
}
if urlparse(url).netloc.split(".")[0] == "tw":
return flag["tw"]
else:
return flag["cn"]

View File

@@ -1,14 +1,27 @@
#!/usr/bin/env python #!/usr/bin/env python3.7
# coding=utf-8 # coding=utf-8
''' '''
# 作者: weimo # 作者: weimo
# 创建日期: 2020-01-04 19:17:44 # 创建日期: 2020-01-04 19:17:44
# 上次编辑时间 : 2020-01-04 19:30:24 # 上次编辑时间: 2020-01-05 14:45:03
# 一个人的命运啊,当然要靠自我奋斗,但是... # 一个人的命运啊,当然要靠自我奋斗,但是...
''' '''
import os import os
from basic.ass import get_ass_head, check_font
from methods.assbase import ASS
from methods.sameheight import SameHeight
def write_one_video_subtitles(file_path, comments, args):
# 对于合集则每次都都得检查一次 也可以放在上一级 放在这里 考虑后面可能特殊指定字体的情况
font_path, font_style_name = check_font(args.font)
ass_head = get_ass_head(font_style_name, args.font_size)
get_xy_obj = SameHeight("那就写这一句作为初始化测试吧!", font_path=font_path, font_size=args.font_size)
subtitle = ASS(file_path, get_xy_obj, font=font_style_name)
for comment in comments:
subtitle.create_new_line(comment)
write_lines_to_file(ass_head, subtitle.lines, file_path)
def write_lines_to_file(ass_head, lines, file_path): def write_lines_to_file(ass_head, lines, file_path):
with open(file_path, "a+", encoding="utf-8") as f: with open(file_path, "a+", encoding="utf-8") as f:
f.write(ass_head + "\n") f.write(ass_head + "\n")

View File

@@ -1,17 +1,18 @@
#!/usr/bin/env python #!/usr/bin/env python3.7
# coding=utf-8 # coding=utf-8
''' '''
# 作者: weimo # 作者: weimo
# 创建日期: 2020-01-04 13:15:25 # 创建日期: 2020-01-04 19:14:43
# 上次编辑时间 : 2020-01-04 17:47:16 # 上次编辑时间: 2020-01-05 14:47:16
# 一个人的命运啊,当然要靠自我奋斗,但是... # 一个人的命运啊,当然要靠自我奋斗,但是...
''' '''
import re import re
import json import json
import requests import requests
from basic.vars import qqlive, iqiyiplayer from time import localtime
from pfunc.cfunc import check_url_locale
from basic.vars import qqlive, iqiyiplayer, chrome
# 放一些仅通过某个id获取另一个/多个id的方法 # 放一些仅通过某个id获取另一个/多个id的方法
@@ -26,7 +27,7 @@ def get_danmu_target_id_by_vid(vid: str):
try: try:
r = requests.get(api_url, params=params, headers=qqlive).content.decode("utf-8") r = requests.get(api_url, params=params, headers=qqlive).content.decode("utf-8")
except Exception as e: except Exception as e:
print("error info -->", e) print("target_id requests error info -->", e)
return None return None
data = json.loads(r.lstrip("QZOutputJson=").rstrip(";")) data = json.loads(r.lstrip("QZOutputJson=").rstrip(";"))
target_id = None target_id = None
@@ -53,7 +54,7 @@ def get_all_vids_by_cid(cid):
try: try:
nomal_ids = json.loads(data["results"][0]["fields"]["nomal_ids"]) nomal_ids = json.loads(data["results"][0]["fields"]["nomal_ids"])
except Exception as e: except Exception as e:
print("error info -->", e) print("load nomal_ids error info -->", e)
return None return None
# F 2是免费 7是会员 0是最新正片之前的预告 4是正片之后的预告 # F 2是免费 7是会员 0是最新正片之前的预告 4是正片之后的预告
vids = [item["V"] for item in nomal_ids if item["F"] in [2, 7]] vids = [item["V"] for item in nomal_ids if item["F"] in [2, 7]]
@@ -63,23 +64,132 @@ def get_all_vids_by_cid(cid):
#-------------------------------------------iqiyi-------------------------------------------- #-------------------------------------------iqiyi--------------------------------------------
def get_vinfos(aid): def get_vinfos(aid, locale="zh_cn"):
api_url = "http://cache.video.iqiyi.com/avlist/{}/0/".format(aid) api_url = "http://cache.video.iqiyi.com/avlist/{}/0/".format(aid)
if locale != "zh_cn":
api_url += "?locale=" + locale
try: try:
r = requests.get(api_url, headers=iqiyiplayer).content.decode("utf-8") r = requests.get(api_url, headers=chrome, timeout=5).content.decode("utf-8")
except Exception as e: except Exception as e:
print("error info -->", e) print("get_vinfos requests error info -->", e)
return None return None
data = json.loads(r[len("var videoListC="):]) data = json.loads(r[len("var videoListC="):])
try: try:
vlist = data["data"]["vlist"] vlist = data["data"]["vlist"]
except Exception as e: except Exception as e:
print("error info -->", e) print("get_vinfos load vlist error info -->", e)
return None return None
vinfos = [[v["shortTitle"] + "_" + str(v["timeLength"]), v["timeLength"], ["id"]] for v in vlist] vinfos = [[v["shortTitle"] + "_" + str(v["timeLength"]), v["timeLength"], ["id"]] for v in vlist]
return vinfos return vinfos
def matchit(patterns, text):
ret = None
for pattern in patterns:
match = re.match(pattern, text)
if match:
ret = match.group(1)
break
return ret
def duration_to_sec(duration):
return sum(x * int(t) for x, t in zip([3600, 60, 1][2 - duration.count(":"):], duration.split(":")))
def get_year_range(aid, locale="zh_cn"):
# 获取第一个和最新一个视频的年份,生成列表返回,遇到任何错误则返回当前年份
year_start = year_end = localtime().tm_year
api_url = "http://pcw-api.iqiyi.com/album/album/baseinfo/{}".format(aid)
if locale != "zh_cn":
api_url += "?locale=" + locale
try:
r = requests.get(api_url, headers=chrome, timeout=5).content.decode("utf-8")
except Exception as e:
print("error info -->", e)
return list(range(year_start, year_end + 1))
data = json.loads(r)["data"]
if data.get("firstVideo"):
year_start = int(data["firstVideo"]["period"][:4])
if data.get("latestVideo"):
year_end = int(data["latestVideo"]["period"][:4])
return list(range(year_start, year_end + 1))
def get_vinfo_by_tvid(tvid, locale="zh_cn"):
api_url = "https://pcw-api.iqiyi.com/video/video/baseinfo/{}".format(tvid)
if locale != "zh_cn":
api_url += "?locale=" + locale
try:
r = requests.get(api_url, headers=chrome, timeout=5).content.decode("utf-8")
except Exception as e:
print("error info -->", e)
return
data = json.loads(r)["data"]
if data.__class__ != dict:
return None
name = data["name"]
duration = data["durationSec"]
return [name + "_" + str(duration), duration, tvid]
def get_vinfos_by_year(aid, years: list, cid=6, locale="zh_cn"):
api_url = "https://pcw-api.iqiyi.com/album/source/svlistinfo?cid={}&sourceid={}&timelist={}".format(cid, aid, ",".join([str(_) for _ in years.copy()]))
if locale != "zh_cn":
api_url += "&locale=" + locale
try:
r = requests.get(api_url, headers=chrome, timeout=5).content.decode("utf-8")
except Exception as e:
print("get_vinfos_by_year error info -->", e)
return None
data = json.loads(r)["data"]
vinfos = []
for year in years:
if year.__class__ != str:
year = str(year)
if data.get(year) is None:
continue
for ep in data[year]:
sec = duration_to_sec(ep["duration"])
vinfos.append([ep["shortTitle"] + "_" + str(sec), sec, ep["tvId"]])
return vinfos
def get_vinfos_by_url(url): def get_vinfos_by_url(url):
pass locale = check_url_locale(url)
patterns = [".+?/w_(\w+?).html", ".+?/v_(\w+?).html", ".+?/a_(\w+?).html", ".+?/lib/m_(\w+?).html"]
isw, isep, isas, isms = [re.match(pattern, url) for pattern in patterns]
if isw is None and isep is None and isas is None and isms is None:
return None
try:
r = requests.get(url, headers=chrome, timeout=5).content.decode("utf-8")
except Exception as e:
print("get_vinfos_by_url error info -->", e)
return None
cid_patterns = ["[\s\S]+?\.cid.+?(\d+)", "[\s\S]+?cid: \"(\d+)\"", "[\s\S]+?channelID.+?\"(\d+)\""]
cid = matchit(cid_patterns, r)
aid_patterns = ["[\s\S]+?aid:'(\d+)'", "[\s\S]+?albumid=\"(\d+)\"", "[\s\S]+?movlibalbumaid=\"(\d+)\"", "[\s\S]+?data-score-tvid=\"(\d+)\""]
aid = matchit(aid_patterns, r)
tvid_patterns = ["[\s\S]+?\"tvid\":\"(\d+)\"", "[\s\S]+?\['tvid'\].+?\"(\d+)\""]
tvid = matchit(tvid_patterns, r)
if cid is None:
cid = ""
elif cid == "6" and isas or isms:#对于综艺合集需要获取年份
# year_patterns = ["[\s\S]+?datePublished.+?(\d\d\d\d)-\d\d-\d\d", "[\s\S]+?data-year=\"(\d+)\""]
# year = matchit(year_patterns, r)
# if year is None:
# years = [localtime().tm_year]
# else:
# years = [year]
years = get_year_range(aid, locale=locale)
else:
pass#暂时没有其他的情况计划特别处理
if isep or isw:
if tvid is None:
return
return get_vinfo_by_tvid(tvid, locale=locale)
if isas or isms:
if aid is None:
return
if cid == "6":
return get_vinfos_by_year(aid, years, locale=locale)
else:
return get_vinfos(aid, locale=locale)
#-------------------------------------------iqiyi-------------------------------------------- #-------------------------------------------iqiyi--------------------------------------------

View File

@@ -1,9 +1,9 @@
#!/usr/bin/env python #!/usr/bin/env python3.7
# coding=utf-8 # coding=utf-8
''' '''
# 作者: weimo # 作者: weimo
# 创建日期: 2019-12-18 09:48:36 # 创建日期: 2020-01-04 19:14:41
# 上次编辑时间 : 2020-01-04 17:54:46 # 上次编辑时间: 2020-01-05 14:45:17
# 一个人的命运啊,当然要靠自我奋斗,但是... # 一个人的命运啊,当然要靠自我奋斗,但是...
''' '''
@@ -15,24 +15,10 @@ from xmltodict import parse
from basic.vars import iqiyiplayer from basic.vars import iqiyiplayer
from basic.ass import check_content from basic.ass import check_content
from pfunc.dump_to_ass import check_file from pfunc.dump_to_ass import check_file, write_one_video_subtitles
from pfunc.request_info import get_vinfos from pfunc.request_info import get_vinfos, get_vinfos_by_url, get_vinfo_by_tvid
def get_vinfo_by_tvid(tvid):
api_url = "https://pcw-api.iqiyi.com/video/video/baseinfo/{}".format(tvid)
try:
r = requests.get(api_url, headers=iqiyiplayer).content.decode("utf-8")
except Exception as e:
print("error info -->", e)
return
data = json.loads(r)["data"]
if data.__class__ != dict:
return None
name = data["name"]
duration = data["durationSec"]
return [name + "_" + str(duration), duration, tvid]
def get_danmu_by_tvid(name, duration, tvid): def get_danmu_by_tvid(name, duration, tvid):
# http://cmts.iqiyi.com/bullet/41/00/10793494100_300_3.z # http://cmts.iqiyi.com/bullet/41/00/10793494100_300_3.z
if tvid.__class__ == int: if tvid.__class__ == int:
@@ -50,7 +36,11 @@ def get_danmu_by_tvid(name, duration, tvid):
except Exception as e: except Exception as e:
print("error info -->", e) print("error info -->", e)
continue continue
try:
raw_xml = decompress(bytearray(r), 15+32).decode('utf-8') raw_xml = decompress(bytearray(r), 15+32).decode('utf-8')
except Exception as e:
index += 1
continue
try: try:
entry = parse(raw_xml)["danmu"]["data"]["entry"] entry = parse(raw_xml)["danmu"]["data"]["entry"]
except Exception as e: except Exception as e:
@@ -62,6 +52,8 @@ def get_danmu_by_tvid(name, duration, tvid):
if entry.__class__ != list: if entry.__class__ != list:
entry = [entry] entry = [entry]
for comment in entry: for comment in entry:
if comment.get("list") is None:
continue
bulletInfo = comment["list"]["bulletInfo"] bulletInfo = comment["list"]["bulletInfo"]
if bulletInfo.__class__ != list: if bulletInfo.__class__ != list:
bulletInfo = [bulletInfo] bulletInfo = [bulletInfo]
@@ -89,10 +81,12 @@ def main(args):
vi = get_vinfos(args.aid) vi = get_vinfos(args.aid)
if vi: if vi:
vinfos += vi vinfos += vi
# if args.url: if args.tvid == "" and args.aid == "" and args.url == "":
# vi = get_vinfos_by_url(args.url) args.url = input("请输入iqiyi链接\n")
# if vi: if args.url:
# vinfos += vi vi = get_vinfos_by_url(args.url)
if vi:
vinfos += vi
subtitles = {} subtitles = {}
for name, duration, tvid in vinfos: for name, duration, tvid in vinfos:
print(name, "开始下载...") print(name, "开始下载...")
@@ -101,5 +95,6 @@ def main(args):
print("跳过{}".format(name)) print("跳过{}".format(name))
return return
comments = get_danmu_by_tvid(name, duration, tvid) comments = get_danmu_by_tvid(name, duration, tvid)
write_one_video_subtitles(file_path, comments, args)
subtitles.update({file_path:comments}) subtitles.update({file_path:comments})
return subtitles return subtitles

View File

@@ -1,9 +1,9 @@
#!/usr/bin/env python #!/usr/bin/env python3.7
# coding=utf-8 # coding=utf-8
''' '''
# 作者: weimo # 作者: weimo
# 创建日期: 2019-12-18 09:37:15 # 创建日期: 2020-01-04 19:14:37
# 上次编辑时间 : 2020-01-04 17:53:28 # 上次编辑时间: 2020-01-05 14:47:36
# 一个人的命运啊,当然要靠自我奋斗,但是... # 一个人的命运啊,当然要靠自我奋斗,但是...
''' '''
@@ -14,7 +14,7 @@ import requests
from basic.vars import qqlive from basic.vars import qqlive
from basic.ass import check_content from basic.ass import check_content
from pfunc.dump_to_ass import check_file from pfunc.dump_to_ass import check_file, write_one_video_subtitles
from pfunc.request_info import get_all_vids_by_cid as get_vids from pfunc.request_info import get_all_vids_by_cid as get_vids
from pfunc.request_info import get_danmu_target_id_by_vid as get_target_id from pfunc.request_info import get_danmu_target_id_by_vid as get_target_id
@@ -158,6 +158,7 @@ def main(args):
subtitles = {} subtitles = {}
for vinfo in vinfos: for vinfo in vinfos:
comments, file_path = get_one_subtitle_by_vinfo(vinfo, args.font, args.font_size, args.y) comments, file_path = get_one_subtitle_by_vinfo(vinfo, args.font, args.font_size, args.y)
write_one_video_subtitles(file_path, comments, args)
subtitles.update({file_path:comments}) subtitles.update({file_path:comments})
return subtitles return subtitles