diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bb8a233 --- /dev/null +++ b/.gitignore @@ -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/ \ No newline at end of file diff --git a/GetDanMu.py b/GetDanMu.py new file mode 100644 index 0000000..eaceada --- /dev/null +++ b/GetDanMu.py @@ -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() \ No newline at end of file diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/basic/ass.py b/basic/ass.py new file mode 100644 index 0000000..4193444 --- /dev/null +++ b/basic/ass.py @@ -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 \ No newline at end of file diff --git a/basic/vars.py b/basic/vars.py new file mode 100644 index 0000000..9e1c45d --- /dev/null +++ b/basic/vars.py @@ -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", +} \ No newline at end of file diff --git a/methods/assbase.py b/methods/assbase.py new file mode 100644 index 0000000..8b94e0a --- /dev/null +++ b/methods/assbase.py @@ -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 \ No newline at end of file diff --git a/methods/sameheight.py b/methods/sameheight.py new file mode 100644 index 0000000..ec2835c --- /dev/null +++ b/methods/sameheight.py @@ -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() \ No newline at end of file diff --git a/pfunc/dump_to_ass.py b/pfunc/dump_to_ass.py new file mode 100644 index 0000000..37eee8b --- /dev/null +++ b/pfunc/dump_to_ass.py @@ -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 \ No newline at end of file diff --git a/pfunc/request_info.py b/pfunc/request_info.py new file mode 100644 index 0000000..7d7d8d8 --- /dev/null +++ b/pfunc/request_info.py @@ -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-------------------------------------------- \ No newline at end of file diff --git a/sites/iqiyi.py b/sites/iqiyi.py new file mode 100644 index 0000000..1afa74b --- /dev/null +++ b/sites/iqiyi.py @@ -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 \ No newline at end of file diff --git a/sites/qq.py b/sites/qq.py new file mode 100644 index 0000000..2df4d2d --- /dev/null +++ b/sites/qq.py @@ -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() \ No newline at end of file