mirror of
https://github.com/xhlove/GetDanMu.git
synced 2025-12-17 00:25:58 +08:00
第一次上传
This commit is contained in:
135
.gitignore
vendored
Normal file
135
.gitignore
vendored
Normal file
@@ -0,0 +1,135 @@
|
||||
# 额外
|
||||
.vscode/
|
||||
releases/
|
||||
*.ass
|
||||
methods/calc_danmu_pos.py
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
pip-wheel-metadata/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
61
GetDanMu.py
Normal file
61
GetDanMu.py
Normal file
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env python
|
||||
# coding=utf-8
|
||||
'''
|
||||
# 作者: weimo
|
||||
# 创建日期: 2020-01-04 12:59:11
|
||||
# 上次编辑时间 : 2020-01-04 17:41:34
|
||||
# 一个人的命运啊,当然要靠自我奋斗,但是...
|
||||
'''
|
||||
|
||||
import sys
|
||||
|
||||
from argparse import ArgumentParser
|
||||
|
||||
from sites.qq import main as qq
|
||||
from sites.iqiyi import main as iqiyi
|
||||
from basic.ass import get_ass_head, check_font
|
||||
from pfunc.dump_to_ass import write_lines_to_file
|
||||
from methods.assbase import ASS
|
||||
from methods.sameheight import SameHeight
|
||||
|
||||
# -------------------------------------------
|
||||
# 基本流程
|
||||
# 1. 根据传入参数确定网站,否则请求输入有关参数或链接。并初始化字幕的基本信息。
|
||||
# 2. 解析链接得到相关视频/弹幕的参数,以及时长等
|
||||
# 3. 根据网站对应接口获取全部弹幕
|
||||
# 4. 转换弹幕
|
||||
# 5. 写入字幕文件
|
||||
# -------------------------------------------
|
||||
|
||||
|
||||
def main():
|
||||
parser = ArgumentParser(description="视频网站弹幕转换/下载工具,任何问题请联系vvtoolbox.dev@gmail.com")
|
||||
parser.add_argument("-f", "--font", default="微软雅黑", help="指定输出字幕字体")
|
||||
parser.add_argument("-fs", "--font-size", default=28, help="指定输出字幕字体大小")
|
||||
parser.add_argument("-s", "--site", default="qq", help="指定网站")
|
||||
parser.add_argument("-cid", "--cid", default="", help="下载cid对应视频的弹幕(腾讯视频合集)")
|
||||
parser.add_argument("-vid", "--vid", default="", help="下载vid对应视频的弹幕,支持同时多个vid,需要用逗号隔开")
|
||||
parser.add_argument("-aid", "--aid", default="", help="下载aid对应视频的弹幕(爱奇艺合集)")
|
||||
parser.add_argument("-tvid", "--tvid", default="", help="下载tvid对应视频的弹幕,支持同时多个tvid,需要用逗号隔开")
|
||||
parser.add_argument("-u", "--url", default="", help="下载视频链接所指向视频的弹幕")
|
||||
parser.add_argument("-y", "--y", action="store_true", help="覆盖原有弹幕而不提示")
|
||||
args = parser.parse_args()
|
||||
print(args.__dict__)
|
||||
font_path, font_style_name = check_font(args.font)
|
||||
ass_head = get_ass_head(font_style_name, args.font_size)
|
||||
if args.site == "qq":
|
||||
subtitles = qq(args)
|
||||
if args.site == "iqiyi":
|
||||
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__":
|
||||
# 打包 --> pyinstaller -F .\qq.py -c -n GetDanMu_qq_1.1
|
||||
main()
|
||||
# subtitle = ASS()
|
||||
0
__init__.py
Normal file
0
__init__.py
Normal file
71
basic/ass.py
Normal file
71
basic/ass.py
Normal file
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env python
|
||||
# coding=utf-8
|
||||
'''
|
||||
# 作者: weimo
|
||||
# 创建日期: 2020-01-04 13:05:23
|
||||
# 上次编辑时间 : 2020-01-04 15:52:11
|
||||
# 一个人的命运啊,当然要靠自我奋斗,但是...
|
||||
'''
|
||||
|
||||
import os
|
||||
|
||||
from basic.vars import fonts
|
||||
|
||||
|
||||
ass_script = """[Script Info]
|
||||
; Script generated by N
|
||||
ScriptType: v4.00+
|
||||
PlayResX: 1920
|
||||
PlayResY: 1080
|
||||
Aspect Ratio: 1920:1080
|
||||
Collisions: Normal
|
||||
WrapStyle: 0
|
||||
ScaledBorderAndShadow: yes
|
||||
YCbCr Matrix: TV.601"""
|
||||
|
||||
ass_style_head = """[V4+ Styles]\nFormat: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding"""
|
||||
ass_style_default = """Style: Default,Arial,20,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,2,2,10,10,10,1"""
|
||||
ass_style_base = """Style:{font},{font},{font_size},&H00FFFFFF,&H66FFFFFF,&H66000000,&H66000000,0,0,0,0,100,100,0.00,0.00,1,1,0,7,0,0,0,0"""
|
||||
ass_events_head = """[Events]\nFormat: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text"""
|
||||
# 基于当前时间范围,在0~1000ms之间停留在(676.571,506.629)处,在1000~3000ms内从位置1300,600移动到360,600(原点在左上)
|
||||
# ass_baseline = """Dialogue: 0,0:20:08.00,0:20:28.00,Default,,0,0,0,,{\t(1000,3000,\move(1300,600,360,600))\pos(676.571,506.629)}这是字幕内容示意"""
|
||||
|
||||
def get_ass_head(font_style_name, font_size):
|
||||
ass_head = ass_script + "\n\n" + ass_style_head + "\n" + ass_style_base.format(font=font_style_name, font_size=font_size) + "\n\n" + ass_events_head
|
||||
return ass_head
|
||||
|
||||
def check_font(font):
|
||||
win_font_path = r"C:\Windows\Fonts"
|
||||
maybe_font_path = os.path.join(win_font_path, font)
|
||||
font_style_name = "微软雅黑"
|
||||
font_path = os.path.join(win_font_path, fonts[font_style_name]) # 默认
|
||||
if os.path.exists(font):
|
||||
# 字体就在当前文件夹 或 完整路径
|
||||
if os.path.isfile(font):
|
||||
if os.path.isabs(font):
|
||||
font_path = font
|
||||
else:
|
||||
font_path = os.path.join(os.getcwd(), font)
|
||||
font_style_name = font[:-os.path.splitext(font)[-1].__len__()]
|
||||
else:
|
||||
pass
|
||||
elif os.path.exists(maybe_font_path):
|
||||
# 给的是字体文件名
|
||||
if os.path.isfile(maybe_font_path):
|
||||
font_path = maybe_font_path
|
||||
font_style_name = font[:-os.path.splitext(font)[-1].__len__()]
|
||||
else:
|
||||
pass
|
||||
elif fonts.get(font):
|
||||
# 别名映射
|
||||
font_path = os.path.join(win_font_path, fonts.get(font))
|
||||
font_style_name = font
|
||||
else:
|
||||
pass
|
||||
return font_path, font_style_name
|
||||
|
||||
def check_content(content: str, comments: list):
|
||||
content = content.replace(" ", "")
|
||||
if content in comments:
|
||||
return
|
||||
return content
|
||||
19
basic/vars.py
Normal file
19
basic/vars.py
Normal file
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env python
|
||||
# coding=utf-8
|
||||
'''
|
||||
# 作者: weimo
|
||||
# 创建日期: 2020-01-04 13:16:18
|
||||
# 上次编辑时间 : 2020-01-04 16:08:34
|
||||
# 一个人的命运啊,当然要靠自我奋斗,但是...
|
||||
'''
|
||||
qqlive = {
|
||||
"User-Agent":"qqlive"
|
||||
}
|
||||
iqiyiplayer = {
|
||||
"User-Agent":"Qiyi List Client PC 7.2.102.1343"
|
||||
}
|
||||
fonts = {
|
||||
"微软雅黑":"msyh.ttc",
|
||||
"微软雅黑粗体":"msyhbd.ttc",
|
||||
"微软雅黑细体":"msyhl.ttc",
|
||||
}
|
||||
68
methods/assbase.py
Normal file
68
methods/assbase.py
Normal file
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env python
|
||||
# coding=utf-8
|
||||
'''
|
||||
# 作者: weimo
|
||||
# 创建日期: 2020-01-04 13:01:04
|
||||
# 上次编辑时间 : 2020-01-04 15:42:02
|
||||
# 一个人的命运啊,当然要靠自我奋斗,但是...
|
||||
'''
|
||||
|
||||
from datetime import datetime
|
||||
from random import randint, choice
|
||||
|
||||
class ASS(object):
|
||||
|
||||
def __init__(self, file_path, get_xy_obj, font="微软雅黑"):
|
||||
self.font = font
|
||||
self.get_xy_obj = get_xy_obj
|
||||
|
||||
# 起点位置可以随机在一个区域出现
|
||||
# 起点位置可以随机在一个区域出现 其他扩展
|
||||
self.baseline = """Dialogue: 0,{start_time},{end_time},{font},,0,0,0,,{{{move_text}{color}}}{text}"""
|
||||
self.lines = []
|
||||
|
||||
def create_new_line(self, comment):
|
||||
text, color, timepoint = comment
|
||||
start_time, end_time, show_time = self.set_start_end_time(timepoint)
|
||||
font = self.set_random_font(line="")
|
||||
move_text = self.set_start_end_pos(text, show_time)
|
||||
color = self.set_color(color)
|
||||
line = self.baseline.format(start_time=start_time, end_time=end_time, font=font, move_text=move_text, color=color, text=text)
|
||||
self.lines.append(line)
|
||||
|
||||
def set_color(self, color: list):
|
||||
# \1c&FDA742&
|
||||
if color.__len__() == 1:
|
||||
color = "\\1c&{}&".format(color[0].lstrip("#").upper())
|
||||
else:
|
||||
color = "\\1c&{}&".format(choice(color).lstrip("#").upper())
|
||||
# color = "\\1c&{}&\\t(0,10000,\\2c&{}&".format(color[0].lstrip("#").upper(), color[1].lstrip("#").upper())
|
||||
return color
|
||||
|
||||
def set_start_end_pos(self, text, show_time):
|
||||
# 考虑不同大小字体下的情况 TODO
|
||||
# \move(1920,600,360,600)
|
||||
# min_index = self.get_min_length_used_y()
|
||||
start_x = 1920
|
||||
width, height, start_y = self.get_xy_obj.get_xy(text, show_time)
|
||||
# start_y = self.all_start_y[min_index]
|
||||
end_x = -(width + randint(0, 30))
|
||||
end_y = start_y
|
||||
move_text = "\\move({},{},{},{})".format(start_x, start_y, end_x, end_y)
|
||||
# self.update_length_used_y(min_index, text.__len__() * 2)
|
||||
return move_text
|
||||
|
||||
def set_start_end_time(self, timepoint):
|
||||
# 40*60*60 fromtimestamp接收的数太小就会出问题
|
||||
t = 144000
|
||||
# 记录显示时间 用于计算字幕运动速度 在某刻的位置 最终决定弹幕分布选择
|
||||
show_time = 15 #randint(10, 20)
|
||||
st = t + timepoint
|
||||
et = t + timepoint + show_time
|
||||
start_time = datetime.fromtimestamp(st).strftime("%H:%M:%S.%f")[1:][:-4]
|
||||
end_time = datetime.fromtimestamp(et).strftime("%H:%M:%S.%f")[1:][:-4]
|
||||
return start_time, end_time, show_time
|
||||
|
||||
def set_random_font(self, line=""):
|
||||
font = self.font
|
||||
return font
|
||||
56
methods/sameheight.py
Normal file
56
methods/sameheight.py
Normal file
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env python
|
||||
# coding=utf-8
|
||||
'''
|
||||
# 作者: weimo
|
||||
# 创建日期: 2019-12-25 20:35:43
|
||||
# 上次编辑时间 : 2019-12-25 23:23:32
|
||||
# 一个人的命运啊,当然要靠自我奋斗,但是...
|
||||
'''
|
||||
|
||||
from PIL.ImageFont import truetype
|
||||
|
||||
class SameHeight(object):
|
||||
'''
|
||||
# 等高弹幕 --> 矩形分割问题?
|
||||
'''
|
||||
def __init__(self, text, font_path="msyh.ttc", font_size=14):
|
||||
self.font = truetype(font=font_path, size=font_size)
|
||||
self.width, self.height = self.get_danmu_size(text)
|
||||
self.height_range = [0, 720]
|
||||
self.width_range = [0, 1920]
|
||||
self.lines_start_y = list(range(*(self.height_range + [self.height])))
|
||||
self.lines_width_used = [[y, 0] for y in self.lines_start_y]
|
||||
self.contents = []
|
||||
|
||||
def get_xy(self, text, show_time):
|
||||
# 在此之前 务必现将弹幕按时间排序
|
||||
self.contents.append([text, show_time])
|
||||
width, height = self.get_danmu_size(text)
|
||||
lines_index = self.get_min_width_used()
|
||||
self.update_width_used(lines_index, width)
|
||||
start_y = self.lines_start_y[lines_index]
|
||||
return width, height, start_y
|
||||
|
||||
def get_min_width_used(self):
|
||||
sorted_width_used = sorted(self.lines_width_used, key=lambda width_used: width_used[1])
|
||||
lines_index = self.lines_width_used.index(sorted_width_used[0])
|
||||
return lines_index
|
||||
|
||||
def update_width_used(self, index, length):
|
||||
self.lines_width_used[index][1] += length
|
||||
|
||||
def get_danmu_size(self, text):
|
||||
# 放在这 不太好 每一次计算都会load下字体
|
||||
text_width, text_height = self.font.getsize(text)
|
||||
return text_width + 2, text_height + 2
|
||||
|
||||
|
||||
def main():
|
||||
text = "测试"
|
||||
show_time = 13
|
||||
sh = SameHeight(text)
|
||||
sh.get_xy(text, show_time)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
33
pfunc/dump_to_ass.py
Normal file
33
pfunc/dump_to_ass.py
Normal file
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env python
|
||||
# coding=utf-8
|
||||
'''
|
||||
# 作者: weimo
|
||||
# 创建日期: 2020-01-04 19:17:44
|
||||
# 上次编辑时间 : 2020-01-04 19:30:24
|
||||
# 一个人的命运啊,当然要靠自我奋斗,但是...
|
||||
'''
|
||||
|
||||
import os
|
||||
|
||||
def write_lines_to_file(ass_head, lines, file_path):
|
||||
with open(file_path, "a+", encoding="utf-8") as f:
|
||||
f.write(ass_head + "\n")
|
||||
for line in lines:
|
||||
f.write(line + "\n")
|
||||
|
||||
def check_file(name, skip=False, fpath=os.getcwd()):
|
||||
flag = True
|
||||
file_path = os.path.join(fpath, name + ".ass")
|
||||
if os.path.isfile(file_path):
|
||||
if skip:
|
||||
os.remove(file_path)
|
||||
else:
|
||||
isremove = input("{}已存在,是否覆盖?(y/n):".format(file_path))
|
||||
if isremove.strip() == "y":
|
||||
os.remove(file_path)
|
||||
else:
|
||||
flag = False
|
||||
return flag, file_path
|
||||
with open(file_path, "wb") as f:
|
||||
pass
|
||||
return flag, file_path
|
||||
85
pfunc/request_info.py
Normal file
85
pfunc/request_info.py
Normal file
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env python
|
||||
# coding=utf-8
|
||||
'''
|
||||
# 作者: weimo
|
||||
# 创建日期: 2020-01-04 13:15:25
|
||||
# 上次编辑时间 : 2020-01-04 17:47:16
|
||||
# 一个人的命运啊,当然要靠自我奋斗,但是...
|
||||
'''
|
||||
|
||||
import re
|
||||
import json
|
||||
import requests
|
||||
|
||||
from basic.vars import qqlive, iqiyiplayer
|
||||
|
||||
# 放一些仅通过某个id获取另一个/多个id的方法
|
||||
|
||||
#---------------------------------------------qq---------------------------------------------
|
||||
|
||||
def get_danmu_target_id_by_vid(vid: str):
|
||||
api_url = "http://bullet.video.qq.com/fcgi-bin/target/regist"
|
||||
params = {
|
||||
"otype":"json",
|
||||
"vid":vid
|
||||
}
|
||||
try:
|
||||
r = requests.get(api_url, params=params, headers=qqlive).content.decode("utf-8")
|
||||
except Exception as e:
|
||||
print("error info -->", e)
|
||||
return None
|
||||
data = json.loads(r.lstrip("QZOutputJson=").rstrip(";"))
|
||||
target_id = None
|
||||
if data.get("targetid"):
|
||||
target_id = data["targetid"]
|
||||
return target_id
|
||||
|
||||
def get_all_vids_by_column_id():
|
||||
# https://s.video.qq.com/get_playsource?id=85603&plat=2&type=4&data_type=3&video_type=10&year=2019&month=&plname=qq&otype=json
|
||||
# 综艺类型的
|
||||
pass
|
||||
|
||||
def get_all_vids_by_cid(cid):
|
||||
api_url = "http://union.video.qq.com/fcgi-bin/data"
|
||||
params = {
|
||||
"tid":"431",
|
||||
"appid":"10001005",
|
||||
"appkey":"0d1a9ddd94de871b",
|
||||
"idlist":cid,
|
||||
"otype":"json"
|
||||
}
|
||||
r = requests.get(api_url, params=params, headers=qqlive).content.decode("utf-8")
|
||||
data = json.loads(r.lstrip("QZOutputJson=").rstrip(";"))
|
||||
try:
|
||||
nomal_ids = json.loads(data["results"][0]["fields"]["nomal_ids"])
|
||||
except Exception as e:
|
||||
print("error info -->", e)
|
||||
return None
|
||||
# F 2是免费 7是会员 0是最新正片之前的预告 4是正片之后的预告
|
||||
vids = [item["V"] for item in nomal_ids if item["F"] in [2, 7]]
|
||||
return vids
|
||||
|
||||
#---------------------------------------------qq---------------------------------------------
|
||||
|
||||
#-------------------------------------------iqiyi--------------------------------------------
|
||||
|
||||
def get_vinfos(aid):
|
||||
api_url = "http://cache.video.iqiyi.com/avlist/{}/0/".format(aid)
|
||||
try:
|
||||
r = requests.get(api_url, headers=iqiyiplayer).content.decode("utf-8")
|
||||
except Exception as e:
|
||||
print("error info -->", e)
|
||||
return None
|
||||
data = json.loads(r[len("var videoListC="):])
|
||||
try:
|
||||
vlist = data["data"]["vlist"]
|
||||
except Exception as e:
|
||||
print("error info -->", e)
|
||||
return None
|
||||
vinfos = [[v["shortTitle"] + "_" + str(v["timeLength"]), v["timeLength"], ["id"]] for v in vlist]
|
||||
return vinfos
|
||||
|
||||
def get_vinfos_by_url(url):
|
||||
pass
|
||||
|
||||
#-------------------------------------------iqiyi--------------------------------------------
|
||||
105
sites/iqiyi.py
Normal file
105
sites/iqiyi.py
Normal file
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env python
|
||||
# coding=utf-8
|
||||
'''
|
||||
# 作者: weimo
|
||||
# 创建日期: 2019-12-18 09:48:36
|
||||
# 上次编辑时间 : 2020-01-04 17:54:46
|
||||
# 一个人的命运啊,当然要靠自我奋斗,但是...
|
||||
'''
|
||||
|
||||
import json
|
||||
import requests
|
||||
|
||||
from zlib import decompress
|
||||
from xmltodict import parse
|
||||
|
||||
from basic.vars import iqiyiplayer
|
||||
from basic.ass import check_content
|
||||
from pfunc.dump_to_ass import check_file
|
||||
from pfunc.request_info import get_vinfos
|
||||
|
||||
|
||||
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):
|
||||
# http://cmts.iqiyi.com/bullet/41/00/10793494100_300_3.z
|
||||
if tvid.__class__ == int:
|
||||
tvid = str(tvid)
|
||||
api_url = "http://cmts.iqiyi.com/bullet/{}/{}/{}_{}_{}.z"
|
||||
timestamp = 300
|
||||
index = 0
|
||||
max_index = duration // timestamp + 1
|
||||
comments = []
|
||||
while index < max_index:
|
||||
url = api_url.format(tvid[-4:-2], tvid[-2:], tvid, timestamp, index + 1)
|
||||
# print(url)
|
||||
try:
|
||||
r = requests.get(url, headers=iqiyiplayer).content
|
||||
except Exception as e:
|
||||
print("error info -->", e)
|
||||
continue
|
||||
raw_xml = decompress(bytearray(r), 15+32).decode('utf-8')
|
||||
try:
|
||||
entry = parse(raw_xml)["danmu"]["data"]["entry"]
|
||||
except Exception as e:
|
||||
index += 1
|
||||
continue
|
||||
# with open("raw_xml.json", "w", encoding="utf-8") as f:
|
||||
# f.write(json.dumps(parse(raw_xml), ensure_ascii=False, indent=4))
|
||||
contents = []
|
||||
if entry.__class__ != list:
|
||||
entry = [entry]
|
||||
for comment in entry:
|
||||
bulletInfo = comment["list"]["bulletInfo"]
|
||||
if bulletInfo.__class__ != list:
|
||||
bulletInfo = [bulletInfo]
|
||||
for info in bulletInfo:
|
||||
content = check_content(info["content"], contents)
|
||||
if content is None:
|
||||
continue
|
||||
else:
|
||||
contents.append(content)
|
||||
color = [info["color"]]
|
||||
comments.append([content, color, int(comment["int"])])
|
||||
print("已下载{:.2f}%".format(index * timestamp * 100 / duration))
|
||||
index += 1
|
||||
comments = sorted(comments, key=lambda _: _[-1])
|
||||
return comments
|
||||
|
||||
|
||||
def main(args):
|
||||
vinfos = []
|
||||
if args.tvid:
|
||||
vi = get_vinfo_by_tvid(args.tvid)
|
||||
if vi:
|
||||
vinfos.append(vi)
|
||||
if args.aid:
|
||||
vi = get_vinfos(args.aid)
|
||||
if vi:
|
||||
vinfos += vi
|
||||
# if args.url:
|
||||
# vi = get_vinfos_by_url(args.url)
|
||||
# if vi:
|
||||
# vinfos += vi
|
||||
subtitles = {}
|
||||
for name, duration, tvid in vinfos:
|
||||
print(name, "开始下载...")
|
||||
flag, file_path = check_file(name, skip=args.y)
|
||||
if flag is False:
|
||||
print("跳过{}".format(name))
|
||||
return
|
||||
comments = get_danmu_by_tvid(name, duration, tvid)
|
||||
subtitles.update({file_path:comments})
|
||||
return subtitles
|
||||
167
sites/qq.py
Normal file
167
sites/qq.py
Normal file
@@ -0,0 +1,167 @@
|
||||
#!/usr/bin/env python
|
||||
# coding=utf-8
|
||||
'''
|
||||
# 作者: weimo
|
||||
# 创建日期: 2019-12-18 09:37:15
|
||||
# 上次编辑时间 : 2020-01-04 17:53:28
|
||||
# 一个人的命运啊,当然要靠自我奋斗,但是...
|
||||
'''
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import requests
|
||||
|
||||
from basic.vars import qqlive
|
||||
from basic.ass import check_content
|
||||
from pfunc.dump_to_ass import check_file
|
||||
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
|
||||
|
||||
|
||||
def get_video_info_by_vid(vids: list):
|
||||
idlist = ",".join(vids)
|
||||
api_url = "http://union.video.qq.com/fcgi-bin/data"
|
||||
params = {
|
||||
"tid":"98",
|
||||
"appid":"10001005",
|
||||
"appkey":"0d1a9ddd94de871b",
|
||||
"idlist":f"{idlist}",
|
||||
"otype":"json"
|
||||
}
|
||||
try:
|
||||
r = requests.get(api_url, params=params, headers=qqlive).content.decode("utf-8")
|
||||
except Exception as e:
|
||||
print("error info -->", e)
|
||||
return
|
||||
data = json.loads(r.lstrip("QZOutputJson=").rstrip(";"))
|
||||
if not data.get("results"):
|
||||
return
|
||||
subkey = ["title", "episode", "langue", "duration"]
|
||||
vinfos = []
|
||||
for index, item in enumerate(data["results"]):
|
||||
vid = [vids[index]]
|
||||
values = [str(item["fields"][key]) for key in subkey if item["fields"].get(key) is not None]
|
||||
name = "_".join(values)
|
||||
if item["fields"].get("duration"):
|
||||
duration = int(item["fields"]["duration"])
|
||||
else:
|
||||
duration = 0
|
||||
# target_id = item["fields"]["targetid"] # 这个target_id一般不是弹幕用的
|
||||
target_id = get_target_id(vid)
|
||||
if target_id is None:
|
||||
continue
|
||||
vinfos.append([vid, name, duration, target_id])
|
||||
# print(vinfos)
|
||||
return vinfos
|
||||
|
||||
def get_danmu_by_target_id(vid: str, duration: int, target_id, font="微软雅黑", font_size=25):
|
||||
# timestamp间隔30s 默认从15开始
|
||||
api_url = "https://mfm.video.qq.com/danmu"
|
||||
params = {
|
||||
"otype":"json",
|
||||
"target_id":"{}&vid={}".format(target_id, vid),
|
||||
"session_key":"0,0,0",
|
||||
"timestamp":15
|
||||
}
|
||||
# subtitle = ASS(file_path, font=font, font_size=font_size)
|
||||
comments = []
|
||||
while params["timestamp"] < duration:
|
||||
try:
|
||||
r = requests.get(api_url, params=params, headers=qqlive).content.decode("utf-8")
|
||||
except Exception as e:
|
||||
print("error info -->", e)
|
||||
continue
|
||||
try:
|
||||
danmu = json.loads(r)
|
||||
except Exception as e:
|
||||
danmu = json.loads(r, strict=False)
|
||||
if danmu.get("count") is None:
|
||||
# timestamp不变 再试一次
|
||||
continue
|
||||
danmu_count = danmu["count"]
|
||||
contents = []
|
||||
for comment in danmu["comments"]:
|
||||
content = check_content(comment["content"], contents)
|
||||
if content is None:
|
||||
continue
|
||||
else:
|
||||
contents.append(content)
|
||||
if comment["content_style"]:
|
||||
style = json.loads(comment["content_style"])
|
||||
if style.get("gradient_colors"):
|
||||
color = style["gradient_colors"]
|
||||
elif style.get("color"):
|
||||
color = style["color"]
|
||||
else:
|
||||
color = ["ffffff"]
|
||||
else:
|
||||
color = ["ffffff"]
|
||||
comments.append([content, color, comment["timepoint"]])
|
||||
print("已下载{:.2f}%".format(params["timestamp"]*100/duration))
|
||||
params["timestamp"] += 30
|
||||
comments = sorted(comments, key=lambda _: _[-1])
|
||||
return comments
|
||||
|
||||
|
||||
def get_one_subtitle_by_vinfo(vinfo, font="微软雅黑", font_size=25, skip=False):
|
||||
vid, name, duration, target_id = vinfo
|
||||
print(name, "开始下载...")
|
||||
flag, file_path = check_file(name, skip=skip)
|
||||
if flag is False:
|
||||
print("跳过{}".format(name))
|
||||
return
|
||||
comments = get_danmu_by_target_id(vid, duration, target_id, font=font, font_size=font_size)
|
||||
# print("{}弹幕下载完成!".format(name))
|
||||
return comments, file_path
|
||||
|
||||
def ask_input(url=""):
|
||||
if url == "":
|
||||
url = input("请输入vid/coverid/链接,输入q退出:\n").strip()
|
||||
if url == "q" or url == "":
|
||||
sys.exit("已结束")
|
||||
# https://v.qq.com/x/cover/m441e3rjq9kwpsc/i0025secmkz.html
|
||||
params = url.replace(".html", "").split("/")
|
||||
if params[-1].__len__() == 11:
|
||||
vids = [params[-1]]
|
||||
elif params[-1].__len__() == 15:
|
||||
cid = params[-1]
|
||||
vids = get_vids(cid)
|
||||
else:
|
||||
vid = url.split("vid=")[-1]
|
||||
if len(vid) != 11:
|
||||
vid = vid.split("&")[0]
|
||||
if len(vid) != 11:
|
||||
sys.exit("没找到vid")
|
||||
vids = [vid]
|
||||
return vids
|
||||
|
||||
|
||||
def main(args):
|
||||
vids = []
|
||||
if args.cid and args.cid.__len__() == 15:
|
||||
vids += get_vids(args.cid)
|
||||
if args.vid:
|
||||
if args.vid.strip().__len__() == 11:
|
||||
vids += [args.vid.strip()]
|
||||
elif args.vid.strip().__len__() > 11:
|
||||
vids += [vid for vid in args.vid.strip().replace(" ", "").split(",") if vid.__len__() == 11]
|
||||
else:
|
||||
pass
|
||||
if args.url:
|
||||
vids += ask_input(url=args.url)
|
||||
if args.vid == "" and args.cid == "" and args.url == "":
|
||||
vids += ask_input()
|
||||
if vids.__len__() <= 0:
|
||||
sys.exit("没有任何有效输入")
|
||||
vinfos = get_video_info_by_vid(vids)
|
||||
subtitles = {}
|
||||
for vinfo in vinfos:
|
||||
comments, file_path = get_one_subtitle_by_vinfo(vinfo, args.font, args.font_size, args.y)
|
||||
subtitles.update({file_path:comments})
|
||||
return subtitles
|
||||
|
||||
# if __name__ == "__main__":
|
||||
# # 打包 --> pyinstaller -F .\qq.py -c -n GetDanMu_qq_1.1
|
||||
# main()
|
||||
# # subtitle = ASS()
|
||||
Reference in New Issue
Block a user