├── .gitignore ├── README.md ├── qito ├── __init__.py ├── __main__.py ├── config │ ├── __init__.py │ └── profile.py ├── ini │ └── __init__.py ├── main.py ├── parse │ ├── __init__.py │ ├── live │ │ ├── 56.py │ │ ├── __init__.py │ │ ├── bilibili.py │ │ ├── douyin.py │ │ ├── douyu.py │ │ ├── huajiao.py │ │ ├── huya.py │ │ └── pps.py │ ├── music │ │ ├── __init__.py │ │ └── qq.py │ ├── playlist │ │ ├── __init__.py │ │ ├── bilibili.py │ │ ├── ixigua.py │ │ ├── le.py │ │ ├── miguvideo.py │ │ ├── qq.py │ │ ├── sohu.py │ │ └── youku.py │ └── video │ │ ├── __init__.py │ │ ├── acfun.py │ │ ├── bilibili.py │ │ ├── cntv.py │ │ ├── douyin.py │ │ ├── douyu.py │ │ ├── fun.py │ │ ├── huya.py │ │ ├── iqiyi.py │ │ ├── ixigua.py │ │ ├── le.py │ │ ├── mgtv.py │ │ ├── miguvideo.py │ │ ├── pptv.py │ │ ├── qq.py │ │ ├── sohu.py │ │ ├── wasu.py │ │ ├── xiaohongshu.py │ │ └── youku.py ├── template.py ├── tool │ ├── __init__.py │ └── javascript │ │ ├── __init__.py │ │ ├── aes.js │ │ ├── crypto-js.min.js │ │ ├── douyin.sign.js │ │ └── iqiyi.cmd5x.js └── util │ ├── __init__.py │ ├── common.py │ ├── execute.py │ └── prepare.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | parse/.DS_Store 3 | .DS_Store 4 | .DS_Store 5 | parse/.DS_Store 6 | .DS_Store 7 | dist/ 8 | qito.egg-info/ 9 | *.pyc 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QITO音乐视频下载器 2 | 3 | qito: 基于python3编写的视频解析器 4 | 5 | --- 6 | 7 | ## 安装使用 8 | 9 | ``` 10 | pip3 install qito 11 | ``` 12 | ## 在线升级 13 | 14 | ``` 15 | qito upgrade 16 | ``` 17 | ## 源升级 18 | ``` 19 | pip3 install --upgrade qito 20 | ``` 21 | ### 全局配置 22 | 23 | ``` 24 | iniPath: 自定义ini文件夹路径, 默认为qito/ini 25 | MAC:qito config -ip xxx路径 26 | 27 | filePath: 自定义文件下载路径, 默认为cwd路径/download 28 | MAC:qito config -fp xxx路径 29 | 30 | ``` 31 | 32 | ### INI配置 33 | 34 | 以qq为例,配置文件名为qq.ini,存放在设定iniPath路径即可 35 | 36 | 如需请求cookie,在ini设置cookie节点即可 37 | 38 | ``` 39 | [cookie] 40 | xxx=xxxx; 41 | ``` 42 | 43 | # 使用说明: 44 | #### 默认打印JSON (Parse) 45 | ``` 46 | MAC: qito https://www.bilibili.com/video/BV1Jx411r76d/ 47 | {'hd': 6, 'type': 'bilibili', 'category': 'video', 'choose': False, 'playlist': False, 'iniPath': False, 'filePath': False, 'urls': ['https://www.bilibili.com/video/BV1Jx411r76d/'], 'parse': 'https://www.bilibili.com/video/BV1Jx411r76d/', 'site': 'bilibili', 'page': 1, 'aid': 9196627, 'title': '【补帧向/AMV】一花一草一世界', 'image': 'http://i0.hdslb.com/bfs/archive/f2b6833f8d732c6b624e11572b9b86afac7d9687.jpg', 'vid': 15195741, 'streams': {'flv': 'http://upos-sz-mirrorhw.bilivideo.com/upgcxcode/41/57/15195741/15195741-1-80.flv', 'segs': [{'url': 'http://upos-sz-mirrorhw.bilivideo.com/upgcxcode/41/57/15195741/15195741-1-80.flv', 'duration': 127.9, 'size': 30695642}], 'mp4': 'http://upos-sz-mirrorhw.bilivideo.com/upgcxcode/41/57/15195741/15195741-1-48.mp4'}, 'playback': 'mp4', 'extra': {'headers': {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:71.0) Gecko/20100101 Firefox/71.0', 'Referer': 'https://www.bilibili.com/video/'}, 'playback': 'flv'}, 'otype': 'video', 'quality': ['流畅 360P', '清晰 480P', '高清 720P', '高清 1080P'], 'multirates': 4, 'ext': 'flv', 'show': '高清 1080P', 'duration': 127.9, 'code': 0} 48 | ``` 49 | #### -f --format : 格式转换 (FFMPEG format) 50 | ``` 51 | MAC: qito https://www.bilibili.com/video/BV1Jx411r76d/ -j 52 | { 53 | "hd": 6, 54 | "type": "bilibili", 55 | "category": "video", 56 | "choose": false, 57 | "playlist": false, 58 | "iniPath": false, 59 | "filePath": false, 60 | "urls": [ 61 | "https://www.bilibili.com/video/BV1Jx411r76d/" 62 | ], 63 | "parse": "https://www.bilibili.com/video/BV1Jx411r76d/", 64 | "site": "bilibili", 65 | "title": "【补帧向/AMV】一花一草一世界", 66 | "vid": 15195741, 67 | "aid": 9196627, 68 | "image": "http://i0.hdslb.com/bfs/archive/f2b6833f8d732c6b624e11572b9b86afac7d9687.jpg", 69 | "page": 1, 70 | "streams": { 71 | "segs": [ 72 | { 73 | "url": "http://upos-sz-mirrorhw.bilivideo.com/upgcxcode/41/57/15195741/15195741-1-80.flv", 74 | "duration": 127.9, 75 | "size": 30695642 76 | } 77 | ], 78 | "flv": "http://upos-sz-mirrorhw.bilivideo.com/upgcxcode/41/57/15195741/15195741-1-80.flv", 79 | "mp4": "http://upos-sz-mirrorhw.bilivideo.com/upgcxcode/41/57/15195741/15195741-1-48.mp4" 80 | }, 81 | "playback": "mp4", 82 | "show": "高清 1080P", 83 | "ext": "flv", 84 | "duration": 127.9, 85 | "otype": "video", 86 | "extra": { 87 | "headers": { 88 | "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:71.0) Gecko/20100101 Firefox/71.0", 89 | "Referer": "https://www.bilibili.com/video/" 90 | }, 91 | "playback": "flv" 92 | }, 93 | "quality": [ 94 | "流畅 360P", 95 | "清晰 480P", 96 | "高清 720P", 97 | "高清 1080P" 98 | ], 99 | "multirates": 4 100 | } 101 | 102 | ``` 103 | 104 | #### -d --download : 下载 (Download) 105 | ``` 106 | MAC: qito https://www.bilibili.com/video/BV1Jx411r76d/ -d 107 | Web: 哔哩哔哩视频(BILIBILI) 108 | Site: bilibili 109 | Title: 补帧向一花一草一世界 110 | Image: http://i0.hdslb.com/bfs/archive/f2b6833f8d732c6b624e11572b9b86afac7d9687.jpg 111 | Vid: 15195741 112 | Parse: https://www.bilibili.com/video/BV1Jx411r76d/ 113 | Category: video 114 | Hd: 6 115 | Stream: 116 | - Ext: flv 117 | Playback: mp4 118 | Duration: 127.9s 119 | Quality: ['流畅 360P', '清晰 480P', '高清 720P', '高清 1080P'] 120 | Show: 高清 1080P 121 | Multirates: 4 122 | Length: 1 123 | Dir: /dw 124 | [1 / 1] |-███████████████------------------|49% 586.04kb/s 14.49M/29.27M 125 | ``` 126 | #### -i --info : 打印信息 (Print Info) 127 | ``` 128 | MAC: qito https://www.bilibili.com/video/BV1Jx411r76d/ -i 129 | Web: 哔哩哔哩视频(BILIBILI) 130 | Site: bilibili 131 | Title: 补帧向一花一草一世界 132 | Image: http://i0.hdslb.com/bfs/archive/f2b6833f8d732c6b624e11572b9b86afac7d9687.jpg 133 | Vid: 15195741 134 | Parse: https://www.bilibili.com/video/BV1Jx411r76d/ 135 | Category: video 136 | Hd: 6 137 | Stream: 138 | - Ext: flv 139 | Playback: mp4 140 | Duration: 127.9s 141 | Quality: ['流畅 360P', '清晰 480P', '高清 720P', '高清 1080P'] 142 | Show: 高清 1080P 143 | Multirates: 4 144 | Length: 1 145 | Location: 146 | - mp4: http://upos-sz-mirrorhw.bilivideo.com/upgcxcode/41/57/15195741/15195741-1-48.mp4 147 | part[1]: http://upos-sz-mirrorhw.bilivideo.com/upgcxcode/41/57/15195741/15195741-1-80.flv [30695642] 148 | ``` 149 | #### -q --query : 只获取链接信息 (Output only video information) 150 | ``` 151 | MAC: qito https://www.bilibili.com/video/BV1Jx411r76d/ -q 152 | {'hd': 6, 'type': 'bilibili', 'category': 'video', 'choose': False, 'playlist': False, 'iniPath': False, 'filePath': False, 'urls': ['https://www.bilibili.com/video/BV1Jx411r76d/'], 'parse': 'https://www.bilibili.com/video/BV1Jx411r76d/', 'site': 'bilibili', 'page': 1, 'title': '【补帧向/AMV】一花一草一世界', 'aid': 9196627, 'vid': 15195741, 'image': 'http://i0.hdslb.com/bfs/archive/f2b6833f8d732c6b624e11572b9b86afac7d9687.jpg', 'code': 0} 153 | 154 | ``` 155 | #### -p --player : 播放器播放 (Directly play the video with PLAYER like mpv) 156 | ``` 157 | MAC: qito https://www.bilibili.com/video/BV1Jx411r76d/ -p mpv 158 | Web: 哔哩哔哩视频(BILIBILI) 159 | Site: bilibili 160 | Title: 【补帧向/AMV】一花一草一世界 161 | Image: http://i0.hdslb.com/bfs/archive/f2b6833f8d732c6b624e11572b9b86afac7d9687.jpg 162 | Vid: 15195741 163 | Parse: https://www.bilibili.com/video/BV1Jx411r76d/ 164 | Category: video 165 | Hd: 6 166 | Stream: 167 | - Ext: flv 168 | Playback: mp4 169 | Duration: 127.9s 170 | Quality: ['流畅 360P', '清晰 480P', '高清 720P', '高清 1080P'] 171 | Show: 高清 1080P 172 | Multirates: 4 173 | Length: 1 174 | Dir: /dw 175 | 176 | PlayBack: http://upos-sz-mirrorhw.bilivideo.com/upgcxcode/41/57/15195741/15195741-1-80.flv 177 | (+) Video --vid=1 (h264 1280x720 60.000fps) 178 | (+) Audio --aid=1 (aac 2ch 48000Hz) 179 | AO: [coreaudio] 48000Hz stereo 2ch floatp 180 | 181 | ``` 182 | ### 额外参数 183 | 184 | | A | 选项 | 传参1 | 传参2 | 类型 | 示例 | 说明 | 185 | |:-----|:----------|:----------|:----------|:----------|:----------|:----------| 186 | | 1 | 分辨率 | -r | --hd | int | 6 | 默认最高分辨率输出 | 187 | | 2 | cookie | -c | --cookie | string | a=b;c=d | 加cookie后,解析会带此cookie请求数据 | 188 | | 3 | 播放列表 | -l | --playlist | bool | | 默认false,带此参数,会优先解析成剧集列表 | 189 | | 4 | 列表选择 | -b | --choose | string | a或a,b或a:b | ,作为分隔符时,会解析a和b集 :作为分隔符时,会解析a到b的剧集 | 190 | | 5 | 自定义文件名 | -n | --name | string | 测试A | 下载时的主体名称是 "测试A" | 191 | | 6 | 调试 | -k | --debug | bool | | 默认false,带此参数后,终端会输出相应请求 | 192 | | 7 | 剧集 | -s | --serial | string | 贝瓦儿歌 | 有此参数时,下载时会存储在存储目录的"贝瓦儿歌"里面 | 193 | 194 | 195 | 196 | -------------------------------------------------------------------------------- /qito/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : __init__.py.py 5 | @Time : 2021/10/19 下午4:43 6 | """ 7 | -------------------------------------------------------------------------------- /qito/__main__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : __main__.py 5 | @Time : 2022/9/10 下午1:39 6 | """ 7 | from argparse import ArgumentParser 8 | 9 | import coloredlogs, re, importlib, traceback, logging, json, sys, os, traceback, time 10 | 11 | sys.path.append(os.path.abspath(os.path.dirname(__file__))) 12 | 13 | from main import Parse 14 | 15 | 16 | def cmd(): 17 | parser = ArgumentParser( 18 | description="A simple video downloader written in python", 19 | usage="qito [OPTIONS] URL [URL...]", 20 | ) 21 | 22 | parser.add_argument( 23 | "-V", "--version", action="store_true", help="Print version and exit" 24 | ) 25 | parser.add_argument( 26 | "-c", "--cookie", type=str, help="Cookie [file.txt/Netscape Cookie]" 27 | ) 28 | parser.add_argument( 29 | "-e", 30 | "--exit", 31 | default=False, 32 | action="store_true", 33 | help="End process when parsing playlist error", 34 | ) 35 | parser.add_argument("-f", "--format", help="Video format") 36 | parser.add_argument( 37 | "-i", "--info", default=False, action="store_true", help="Print all information" 38 | ) 39 | parser.add_argument( 40 | "-j", "--json", default=False, action="store_true", help="Print json" 41 | ) 42 | 43 | parser.add_argument( 44 | "-q", 45 | "--query", 46 | default=False, 47 | action="store_true", 48 | help="Print video information only", 49 | ) 50 | parser.add_argument( 51 | "-s", 52 | "--serial", 53 | type=str, 54 | help="Stored in the serial folder when downloading ", 55 | ) 56 | parser.add_argument( 57 | "-r", "--hd", type=int, help="Video resolution selection [1,2, ...]" 58 | ) 59 | parser.add_argument( 60 | "-k", "--debug", default=False, action="store_true", help="Print messages" 61 | ) 62 | 63 | parser.add_argument("-t", "--type", help="Parsing type,Add when urls is not a URL") 64 | 65 | parser.add_argument("-v", "--category", help="Site category [video/music/live]") 66 | parser.add_argument( 67 | "-x", "--proxy", default=False, help="Proxy option [http/https/socks5]" 68 | ) 69 | parser.add_argument("-y", "--http", default=False, help="Http proy") 70 | parser.add_argument( 71 | "-z", 72 | "--timeout", 73 | default=False, 74 | type=int, 75 | help="Set httpGet timeout seconds, default 6s", 76 | ) 77 | playlist_grp = parser.add_argument_group("Playlist options") 78 | 79 | playlist_grp.add_argument( 80 | "-b", 81 | "--choose", 82 | default=False, 83 | help="Choose which range when the urls is a playlist", 84 | ) 85 | playlist_grp.add_argument( 86 | "-l", 87 | "--playlist", 88 | default=False, 89 | action="store_true", 90 | help="Add when urls is playlist", 91 | ) 92 | 93 | download_grp = parser.add_argument_group("Download options") 94 | download_grp.add_argument( 95 | "-d", "--download", default=False, action="store_true", help="Download" 96 | ) 97 | 98 | download_grp.add_argument( 99 | "-m", "--merge", default=True, action="store_false", help="Do not merge videos" 100 | ) 101 | download_grp.add_argument( 102 | "-n", 103 | "--name", 104 | default=False, 105 | type=str, 106 | help="Video downloaded with the FileName you want", 107 | ) 108 | download_grp.add_argument( 109 | "-u", 110 | "--multi", 111 | default=False, 112 | type=int, 113 | help="Multi-threaded download, default 10", 114 | ) 115 | download_grp.add_argument("-w", "--dir", default=False, help="Storage folder") 116 | download_grp.add_argument( 117 | "-a", 118 | "--capture", 119 | default=False, 120 | action="store_true", 121 | help=" Download clips only", 122 | ) 123 | player_grp = parser.add_argument_group("Player options") 124 | player_grp.add_argument( 125 | "-g", 126 | "--geometry", 127 | default=False, 128 | help="Adjust the initial window position or size,For example,1366 or 50﹪", 129 | ) 130 | player_grp.add_argument( 131 | "-lo", 132 | "--loop", 133 | default=False, 134 | help="Loop", 135 | ) 136 | player_grp.add_argument( 137 | "-p", 138 | "--player", 139 | default=False, 140 | help="Directly play the video with PLAYER like mpv", 141 | ) 142 | 143 | player_grp.add_argument( 144 | "-fs", 145 | "--fullscreen", 146 | default=False, 147 | action="store_true", 148 | help="Fullscreen playback", 149 | ) 150 | player_grp.add_argument( 151 | "-ts", "--start", default=False, help="Seek to given time position" 152 | ) 153 | player_grp.add_argument( 154 | "-te", "--end", default=False, help="Stop at given absolute time" 155 | ) 156 | player_grp.add_argument( 157 | "-tl", 158 | "--length", 159 | default=False, 160 | help="Stop after a given time relative to the start time", 161 | ) 162 | player_grp.add_argument( 163 | "-na", 164 | "--no_audio", 165 | default=False, 166 | action="store_true", 167 | help="Do not play sound", 168 | ) 169 | player_grp.add_argument( 170 | "-nv", 171 | "--no_video", 172 | default=False, 173 | action="store_true", 174 | help="Do not play video", 175 | ) 176 | player_grp.add_argument( 177 | "-vo", 178 | "--volume", 179 | default=False, 180 | help="Volume", 181 | ) 182 | extra_grp = parser.add_argument_group("Extra options") 183 | extra_grp.add_argument( 184 | "-in", 185 | "--init", 186 | default=False, 187 | help="Initial setup, backup or restore user.py", 188 | ) 189 | extra_grp.add_argument( 190 | "-o", "--language", default=False, help="Select language options" 191 | ) 192 | # 额外 193 | extra_grp.add_argument( 194 | "-it", "--itag", default=False, help="Youtube itag, 137[,value]" 195 | ) 196 | extra_grp.add_argument("-ccode", "--ccode", default=False, help="Youku ccode") 197 | extra_grp.add_argument("-ip", "--iniPath", default=False, help="Config iniPath") 198 | extra_grp.add_argument("-fp", "--filePath", default=False, help="Config filePath") 199 | extra_grp.add_argument( 200 | "-pw", "--password", default=False, help="Encryption password for video" 201 | ) 202 | extra_grp.add_argument( 203 | "-en", 204 | "--encoder", 205 | default=False, 206 | help="Select the video option to encode", 207 | ) 208 | 209 | parser.add_argument("urls", type=str, nargs="*", help="video urls") 210 | 211 | args = parser.parse_args() 212 | if args.version: 213 | from config.profile import version 214 | 215 | sys.exit(version) 216 | elif args.capture: 217 | args.download = True 218 | if not args.start: 219 | print("Please set the start time") 220 | sys.exit() 221 | return args 222 | 223 | 224 | def main(): 225 | params = vars(cmd()) 226 | if not params["urls"]: 227 | sys.exit("usage: qito [OPTION]... URL...") 228 | 229 | # logging.basicConfig(handlers=[log.ColorHandler()]) 230 | FIELD_STYLES = dict( 231 | asctime=dict(color="green"), 232 | hostname=dict(color="magenta"), 233 | levelname=dict(color="green"), 234 | filename=dict(color="magenta"), 235 | name=dict(color="red"), 236 | threadName=dict(color="green"), 237 | ) 238 | 239 | LEVEL_STYLES = dict( 240 | debug=dict(color="green"), 241 | info=dict(color="cyan"), 242 | warning=dict(color="yellow"), 243 | error=dict(color="red"), 244 | critical=dict(color="red"), 245 | ) 246 | 247 | coloredlogs.install( 248 | level="DEBUG", 249 | fmt="%(levelname)s:%(filename)s:%(funcName)s:%(lineno)d:%(message)s", 250 | level_styles=LEVEL_STYLES, 251 | field_styles=FIELD_STYLES, 252 | ) 253 | if params.get("debug"): 254 | logging.root.setLevel(logging.DEBUG) 255 | logging.getLogger("urllib3").setLevel(logging.WARNING) 256 | logging.getLogger("chardet").setLevel(logging.WARNING) 257 | else: 258 | logging.root.setLevel(logging.WARNING) 259 | try: 260 | for parse in params["urls"]: 261 | params["parse"] = parse 262 | Parse(params) 263 | except (AssertionError, IndexError) as e: 264 | info = sys.exc_info() 265 | try: 266 | err = traceback.extract_tb(info[2])[1] 267 | except: 268 | err = traceback.extract_tb(info[2])[0] 269 | error = { 270 | "message": str(e), 271 | "file": err[0], 272 | "line": err[1], 273 | "function": err[2], 274 | "code": err[3], 275 | } 276 | logging.warning(error) 277 | # logging.error(e.message) 278 | except (NotImplementedError, KeyError, NameError, AttributeError, TypeError) as e: 279 | logging.error(e.message) 280 | if params["exit"]: 281 | sys.exit() 282 | except KeyboardInterrupt: 283 | print("\r\n") 284 | logging.warning("ctrl c!") 285 | sys.exit() 286 | 287 | 288 | if __name__ == "__main__": 289 | main() 290 | -------------------------------------------------------------------------------- /qito/config/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : __init__.py.py 5 | @Time : 2021/10/19 下午4:43 6 | """ 7 | -------------------------------------------------------------------------------- /qito/config/profile.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : profile.py 5 | @Time : 2022/2/13 下午4:48 6 | """ 7 | version = "1.0.5" 8 | jsonFilter = "version|cookie|dir|exit|format|info|json|query|debug|proxy|http|timeout|download|merge|multi|dir|capture|geometry|loop|player|fullscreen|start|end|length|no_audio|no_video|volume|init|language|itag|ccode|password|encoder|name|context" 9 | -------------------------------------------------------------------------------- /qito/ini/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : __init__.py.py 5 | @Time : 2022/8/30 下午6:34 6 | """ 7 | -------------------------------------------------------------------------------- /qito/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # -*- coding: utf-8 -*- 4 | """ 5 | @File : main.py 6 | @Time : 2022/2/13 下午6:14 7 | """ 8 | import time, importlib, os, re, logging, sys, requests, shutil 9 | from pathlib import Path 10 | 11 | from util import common 12 | 13 | 14 | class Parse(common.Common): 15 | def __init__(self, params): 16 | super().__init__() 17 | self.abspath = os.path.abspath(os.path.dirname(__file__)) 18 | self.cwd = os.getcwd() 19 | self.exit = 0 20 | if params.get("debug"): 21 | logging.basicConfig( 22 | format="%(levelname)s:%(filename)s:%(funcName)s:%(lineno)d:%(message)s", 23 | level=logging.DEBUG, 24 | ) 25 | 26 | logging.getLogger("urllib3").setLevel(logging.WARNING) 27 | logging.getLogger("chardet").setLevel(logging.WARNING) 28 | else: 29 | logging.basicConfig( 30 | format="%(levelname)s:%(filename)s:%(funcName)s:%(lineno)d:%(message)s", 31 | level=logging.WARNING, 32 | ) 33 | 34 | self.configuration() 35 | if params["parse"] == "config": 36 | if not Path(f"{self.abspath}/config/config.py").exists(): 37 | content = 'iniPath=""\nfilePath=""' 38 | self.write(f"{self.abspath}/config/config.py", content) 39 | print("Created: config.py created successfully") 40 | else: 41 | print("Exists: config.py already exists") 42 | 43 | from config import config 44 | 45 | if params.get("iniPath"): 46 | config.iniPath = params["iniPath"] 47 | if params.get("filePath"): 48 | config.filePath = params["filePath"] 49 | 50 | content = "\n".join( 51 | [ 52 | f'{i}="{getattr(config, i, None)}"' 53 | for i in [e for e in dir(config) if not e.startswith("_")] 54 | ] 55 | ) 56 | self.write(f"{self.abspath}/config/config.py", content) 57 | elif params["parse"] == "upgrade": 58 | try: 59 | file = self.abspath + "/qito-main.zip" 60 | if os.path.exists(file): 61 | os.remove(file) 62 | print( 63 | "Downloading github package [https://github.com/qitoqito/qito/archive/refs/heads/main.zip]..." 64 | ) 65 | r = requests.get( 66 | "https://github.com/qitoqito/qito/archive/refs/heads/main.zip" 67 | ) 68 | 69 | with open(file, "wb") as f: 70 | f.write(r.content) 71 | f.close() 72 | 73 | except: 74 | pass 75 | 76 | if os.path.exists(file): 77 | import zipfile 78 | import shutil 79 | 80 | filePath = self.abspath + "/qito-main" 81 | if os.path.exists(filePath): 82 | shutil.rmtree(filePath) 83 | zip = zipfile.ZipFile(file) 84 | print("The zip download is complete and will be unzipped soon...") 85 | for name in zip.namelist(): 86 | zip.extract(name, self.abspath) 87 | 88 | print("Moving files ...") 89 | if sys.version_info < (3, 8): 90 | shutil.copytree( 91 | self.abspath + "/qito-main/qito", 92 | self.abspath, 93 | ) 94 | else: 95 | shutil.copytree( 96 | self.abspath + "/qito-main/qito", 97 | self.abspath, 98 | dirs_exist_ok=True, 99 | ) 100 | print("exit...") 101 | else: 102 | print("Download failed...") 103 | 104 | elif params["parse"] == "ini": 105 | pass 106 | elif params.get("playlist"): 107 | self.playList(params) 108 | else: 109 | self.working(params) 110 | 111 | def configuration(self): 112 | self.getConfig("config") 113 | self.getConfig("profile") 114 | self.getConfig("user") 115 | if self.get("iniPath"): 116 | ini = self.parseIni(f"{self.iniPath}/config.ini") 117 | else: 118 | ini = self.parseIni(f"{self.abspath}/ini/config.ini") 119 | 120 | def working(self, params): 121 | params["category"] = params.get("category") or self.get("category") or "video" 122 | params["hd"] = params.get("hd") or self.get("hd") or 6 123 | params["parse"] = str(params["parse"]) 124 | if self.hasurl(params["parse"]): 125 | site = self.domain(params["parse"]) 126 | if site in self.prepare_location(): 127 | params["parse"] = self.curl( 128 | { 129 | "url": params["parse"], 130 | "response": "location", 131 | } 132 | ) 133 | type = self.prepare_change(site) 134 | 135 | for k, v in self.prepare_category().items(): 136 | if site in v: 137 | if self.match(v[site], params["parse"]): 138 | params["category"] = k 139 | break 140 | else: 141 | for k1, v1 in v.items(): 142 | if self.match(v1, params["parse"]): 143 | type = k1 144 | params["category"] = k 145 | site = k1 146 | break 147 | 148 | else: 149 | site = params["type"] 150 | 151 | type = self.prepare_change(site) 152 | 153 | if self.get("site") and site in self.siteChange.keys(): 154 | type = self.siteChange[site] 155 | 156 | params["site"] = site 157 | params["type"] = type 158 | try: 159 | imp = importlib.import_module(f"parse.{params['category']}.{type}") 160 | 161 | self.imp = imp 162 | a = imp.Main() 163 | 164 | a.init(params) 165 | except ModuleNotFoundError as e: 166 | print( 167 | f'The parsing of {params["category"]}.{params["site"]} is not supported...' 168 | ) 169 | except ValueError as e: 170 | print(e) 171 | 172 | except KeyboardInterrupt: 173 | # ctrl + c 终止运行 174 | print("\r\n") 175 | self.exit = 1 176 | logging.warning("End Process") 177 | sys.exit() 178 | 179 | def playList(self, params): 180 | domain = self.match(r"(\w+(?:-\w+)*).\w+\/", params["parse"]) 181 | 182 | type = params.get("type") or domain 183 | category = params.get("category") or "video" 184 | # try: 185 | 186 | try: 187 | imp = importlib.import_module(f"parse.playlist.{type}") 188 | a = imp.Main() 189 | data = getattr(a, f"{category}List")(params) 190 | assert len(data["data"]) > 0, "lists" 191 | params["category"] = data.get("category") 192 | params["type"] = data.get("type") 193 | 194 | params["serial"] = self.sub( 195 | "[’!\"#$%&'()*+,./:;<=>?@,。?★、…【】《》?“”‘’![\\]^`{|}~]+", 196 | " ", 197 | params.get("serial") or data.get("serial"), 198 | ).strip() 199 | if params.get("choose"): 200 | params["choose"] = str(params["choose"]) 201 | if ":" in params["choose"]: 202 | spl = [i for i in params["choose"].split(":") if i] 203 | start = int(spl[0]) - 1 204 | 205 | if len(spl) > 1: 206 | parseLists = data["data"][start : int(spl[1])] 207 | else: 208 | parseLists = data["data"][start:] 209 | 210 | else: 211 | spl = params["choose"].split(",") 212 | parseLists = [] 213 | for i in spl: 214 | try: 215 | parseLists.append(data["data"][int(i) - 1]) 216 | except: 217 | pass 218 | assert len(parseLists) > 0, "Exceeds the length of the episode..." 219 | else: 220 | parseLists = data["data"] 221 | params["total"] = len(data["data"]) 222 | except: 223 | parseLists = [params["parse"]] 224 | for i in parseLists: 225 | if isinstance(i, dict): 226 | params = {**params, **i} 227 | else: 228 | params["parse"] = i 229 | if self.exit: 230 | sys.exit("Exit...") 231 | if params.get("exit"): 232 | self.working(params) 233 | else: 234 | try: 235 | self.working(params) 236 | except: 237 | pass 238 | # except: 239 | # pass 240 | -------------------------------------------------------------------------------- /qito/parse/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : __init__.py.py 5 | @Time : 2021/10/19 下午5:47 6 | """ 7 | -------------------------------------------------------------------------------- /qito/parse/live/56.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : 56.py 5 | @Time : 2022/12/1 下午9:58 6 | """ 7 | import template 8 | 9 | 10 | class Main(template.Template): 11 | def __init__(self): 12 | super().__init__() 13 | self.title = "千帆直播(56)" 14 | 15 | def query(self): 16 | p = self.params 17 | if p["parse"].startswith("http"): 18 | vid = self.match("\/(\d+)", p["parse"]) 19 | else: 20 | vid = p["parse"] 21 | if p.get("query"): 22 | url = f"https://qf.56.com/{vid}?union=56_home" 23 | html = self.curl(url) 24 | anchorInfo = self.match("pageInfo.anchor\s*=\s*([^\}]+)", html) 25 | if anchorInfo: 26 | anchor = self.match("nickName:\s*'([^']+)',", anchorInfo) 27 | title = f"{anchor}的直播间" 28 | image = self.match("cover:\s*'([^']+)',", anchorInfo) 29 | 30 | return self.compact() 31 | 32 | def parse(self): 33 | p = self.params 34 | assert p["vid"], "vid" 35 | vid = p["vid"] 36 | url = f"https://qf.56.com/{vid}?union=56_home" 37 | html = self.curl(url) 38 | anchorRoom = self.match("pageInfo.anchorRoom\s*=\s*([^\}]+)", html) 39 | assert anchorRoom, "data" 40 | 41 | isLive = self.match("roomLive:\s*'([^']+)'", anchorRoom) 42 | assert isLive == "1", "close" 43 | 44 | anchorInfo = self.match("pageInfo.anchor\s*=\s*([^\}]+)", html) 45 | if anchorInfo: 46 | anchor = self.match("nickName:\s*'([^']+)',", anchorInfo) 47 | title = f"{anchor}的直播间" 48 | image = self.match("cover:\s*'([^']+)',", anchorInfo) 49 | 50 | match = self.matchAll("(rUrl|hUrl|lUrl)\s*:\s*'([^']+)'", html) 51 | lists = dict(match) 52 | 53 | assert lists, "lists" 54 | 55 | qualitys = list(lists.keys()) 56 | 57 | try: 58 | qualitys.sort(key=["lUrl", "hUrl", "rUrl"].index) 59 | except: 60 | pass 61 | dicts = {"lUrl": "标清", "hUrl": "高清", "rUrl": "超清"} 62 | quality = [dicts[i] for i in qualitys] 63 | show = self.data(quality, p["hd"]) 64 | flv = self.sub( 65 | ["\/\/(.*?)\/live", "\?", "get_url=\d+"], 66 | ["https://v-ngb.qf.56.com/live", ".flv?", "get_url=10"], 67 | lists[self.data(qualitys, p["hd"])], 68 | ) 69 | 70 | ext = playback = "flv" 71 | return self.compact() 72 | -------------------------------------------------------------------------------- /qito/parse/live/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : __init__.py.py 5 | @Time : 2022/9/11 下午4:03 6 | """ 7 | -------------------------------------------------------------------------------- /qito/parse/live/bilibili.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : bilibili.py 5 | @Time : 2022/11/17 下午3:48 6 | """ 7 | import template 8 | 9 | 10 | class Main(template.Template): 11 | def __init__(self): 12 | super().__init__() 13 | self.title = "()" 14 | self.require = ["importlib"] 15 | 16 | def query(self): 17 | p = self.params 18 | if p["parse"].startswith("http"): 19 | vid = self.match("\/(\d+)", p["parse"]) 20 | html = self.curl(p["parse"]) 21 | 22 | cid = self.match('"room_id":(\d+)', html) or vid 23 | else: 24 | cid = p["parse"] 25 | assert cid, "cid" 26 | 27 | if p.get("query"): 28 | url = f"https://api.live.bilibili.com/xlive/web-room/v1/index/getInfoByRoom?room_id={cid}" 29 | getinfo = self.curl(url) 30 | self.logging.debug(f"getInfo: {getinfo} \r\n") 31 | json = self.loads(getinfo) 32 | if json["code"] == 0: 33 | title = json["data"]["room_info"]["title"] 34 | image = json["data"]["room_info"]["keyframe"] 35 | anchor = json["data"]["anchor_info"]["base_info"]["uname"] 36 | return self.compact() 37 | 38 | def parse(self): 39 | p = self.params 40 | assert p["cid"], "cid" 41 | cid = p["cid"] 42 | 43 | url = f"https://api.live.bilibili.com/xlive/web-room/v1/index/getInfoByRoom?room_id={cid}" 44 | getinfo = self.curl(url) 45 | self.logging.debug(f"getInfo: {getinfo} \r\n") 46 | json = self.loads(getinfo) 47 | 48 | assert json["code"] == 0, "data" 49 | 50 | title = json["data"]["room_info"]["title"] 51 | image = json["data"]["room_info"]["keyframe"] 52 | anchor = json["data"]["anchor_info"]["base_info"]["uname"] 53 | # assert json["data"]["room_info"]["live_status"] == 1, "close" 54 | liveStatus = json["data"]["room_info"]["live_status"] 55 | 56 | if liveStatus == 0: 57 | raise NotImplementedError("close") 58 | elif liveStatus == 2: 59 | 60 | self.params["category"] = "video" 61 | 62 | url = f"https://api.live.bilibili.com/live/getRoundPlayVideo?room_id={cid}&type=flv&a=0.9907983099110425" 63 | html = self.curl(url) 64 | self.logging.debug(f"getExtra: {html} \r\n") 65 | json = self.loads(html) 66 | assert json["code"] == 0, "data" 67 | 68 | title = json["data"]["title"] 69 | videoCid = json["data"]["cid"] 70 | imp = self.modules["importlib"].import_module("parse.video.bilibili") 71 | a = imp.Main() 72 | a.params = { 73 | "vid": videoCid, 74 | "hd": p["hd"], 75 | } 76 | return a.parse() 77 | 78 | elif liveStatus == 1: 79 | 80 | # ary = {"150": "高清", "250": "超清", "400": "蓝光", "10000": "原画"} 81 | # key = list(ary.keys()) 82 | # quality = list(ary.values()) 83 | # qn = self.data(key, p['hd']) 84 | # show = self.data(quality, p['hd']) 85 | # 86 | # url = 'https://api.live.bilibili.com/room/v1/Room/playUrl?cid=%s&qn=%s&platform=web' % (vid, qn) 87 | qn = 1000 88 | 89 | for i in range(2): 90 | url = ( 91 | "https://api.live.bilibili.com/room/v1/Room/playUrl?cid=%s&qn=%s&platform=pc" 92 | % (cid, qn) 93 | ) 94 | html = self.curl(url) 95 | 96 | json = self.loads(html) 97 | assert json["code"] == 0, "data" 98 | quality_description = json["data"]["quality_description"][::-1] 99 | qns = [i["qn"] for i in quality_description] 100 | quality = [i["desc"] for i in quality_description] 101 | qn = self.data(qns, p["hd"]) 102 | if qn == 1000: 103 | break 104 | self.logging.debug(f"getLive: {html} \r\n") 105 | show = self.data(quality, p["hd"]) 106 | flv = json["data"]["durl"][0]["url"] 107 | ext = playback = "flv" 108 | extra = { 109 | "headers": { 110 | "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:71.0) Gecko/20100101 Firefox/71.0", 111 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", 112 | "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2", 113 | "Referer": url, 114 | } 115 | } 116 | return self.compact() 117 | -------------------------------------------------------------------------------- /qito/parse/live/douyin.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : douyin.py 5 | @Time : 2022/9/11 下午4:03 6 | """ 7 | import template 8 | 9 | 10 | class Main(template.Template): 11 | def __init__(self): 12 | super().__init__() 13 | self.title = "抖音直播(DOUYIN)" 14 | self.require = [("quickjs", "Function", "fn"), "execjs", "importlib"] 15 | 16 | def query(self): 17 | p = self.params 18 | nonece = self.md5(self.timestamp)[:21] 19 | if self.hasurl(p["parse"]): 20 | if "live.douyin.com" in p["parse"]: 21 | html = self.curl( 22 | { 23 | "url": p["parse"], 24 | "cookie": f"__ac_nonce={nonece}; __ac_signature={self.getSign(nonece)};", 25 | } 26 | ) 27 | vid = self.match(["room_id%3D(\d+)", "roomId%22%3A%22(\d+)"], html) 28 | # vid = self.match("\/(\d+)", p["parse"]) 29 | else: 30 | url = self.curl({"url": p["parse"], "response": "location"}) 31 | vid = self.match("\/(\d+)", url) 32 | 33 | else: 34 | vid = p["parse"] 35 | if p.get("query"): 36 | url = f"https://webcast.amemv.com/webcast/room/reflow/info/?verifyFp=&type_id=0&live_id=1&room_id={vid}&sec_user_id=&app_id=1128&msToken=" 37 | for i in range(3): 38 | html = self.curl( 39 | { 40 | "url": url, 41 | # 'from':'', 42 | } 43 | ) 44 | if html: 45 | break 46 | data = self.jsonParse(html) 47 | try: 48 | data = self.jsonParse(html) 49 | room = self.haskey(data, "data.room") 50 | title = room["title"] 51 | image = room["cover"]["url_list"][0] 52 | anchor = room["owner"]["nickname"] 53 | except: 54 | pass 55 | return self.compact() 56 | 57 | def parse(self): 58 | p = self.params 59 | assert p["vid"], "vid" 60 | vid = p["vid"] 61 | extra = {"headers": {"remove": 1}} 62 | nonece = self.md5(self.timestamp)[:21] 63 | 64 | content = self.curl( 65 | { 66 | "url": p["parse"], 67 | "cookie": f"__ac_nonce={nonece}; __ac_signature={self.getSign(nonece)};", 68 | } 69 | ) 70 | render = self.match(r'id="RENDER_DATA"\s*.+?\>([^\<]+)', content) 71 | data = self.loads(self.unquote(render)) 72 | room = self.haskey(data, "app.initialState.roomStore.roomInfo.room") 73 | 74 | assert self.haskey(room, "status") == 2, "close" 75 | title = room["title"] 76 | image = room["cover"]["url_list"][0] 77 | anchor = room["owner"]["nickname"] 78 | stream = "" 79 | 80 | if "fifa_skin" in self.haskey(room, "background.uri") and self.haskey( 81 | room, "episode_extra.match_room_info.match_data.match_id" 82 | ): 83 | roomId = self.haskey( 84 | room, "episode_extra.match_room_info.match_data.match_id" 85 | ) 86 | html = self.curl( 87 | { 88 | "url": f"https://live.douyin.com/fifaworldcup/{roomId}?enter_from_merge=web_share_link&enter_method=web_share_link" 89 | } 90 | ) 91 | json = self.match(r'id="RENDER_DATA"\s*.+?>([^\<]+)', html) 92 | data = self.jsonParse(self.unquote(json)) 93 | for k, v in data.items(): 94 | a = self.jsonParse(v) 95 | if self.haskey(a, "roomRes"): 96 | room2 = self.haskey(a, "roomRes.newRoomInfo.room") 97 | stream = room2["stream_url"] 98 | camera = self.haskey( 99 | a, "roomRes.newRoomInfo.room.episode_extra.camera_infos" 100 | ) 101 | if camera: 102 | lang = [] 103 | eee = 1 104 | for abc in camera: 105 | lang.append( 106 | { 107 | "url": f"https://live.douyin.com/fifaworldcup/{roomId}.html", 108 | "langcode": eee, 109 | "language": abc["title"], 110 | } 111 | ) 112 | eee += 1 113 | if p.get("language") and ( 114 | p["language"] == abc["title"] 115 | or p["language"] == str(eee) 116 | ): 117 | stream = abc["stream_info"] 118 | language = abc["title"] 119 | extra["language"] = lang 120 | 121 | # 122 | 123 | if not stream: 124 | stream = room["stream_url"] 125 | 126 | try: 127 | streamData = self.loads( 128 | stream["live_core_sdk_data"]["pull_data"]["stream_data"] 129 | )["data"] 130 | 131 | qualitys = list( 132 | filter( 133 | lambda x: x in streamData.keys(), 134 | ["md", "ld", "sd", "hd", "uhd", "origin"], 135 | ) 136 | ) 137 | 138 | qualitys.sort(key=["md", "ld", "sd", "hd", "uhd", "origin"].index) 139 | show1 = self.data(qualitys, p["hd"]) 140 | dict = { 141 | "md": "清晰", 142 | "ld": "标清", 143 | "sd": "高清", 144 | "hd": "超清", 145 | "uhd": "蓝光", 146 | "origin": "原画", 147 | } 148 | quality = [dict[i] for i in qualitys] 149 | show = dict[show1] 150 | main = streamData[show1]["main"] 151 | flv = main["flv"] 152 | m3u8 = main["hls"] 153 | except: 154 | resolution_name = stream["resolution_name"] 155 | flvUrl = stream["flv_pull_url"] 156 | qualitys = list(flvUrl.keys())[::-1] 157 | qualitys.sort(key=["SD1", "SD2", "HD1", "FULL_HD1", "ORIGION"].index) 158 | quality = [resolution_name[i] for i in qualitys] 159 | show = self.data(quality, p["hd"]) 160 | resolution = self.data(qualitys, p["hd"]) 161 | flv = flvUrl[resolution] 162 | m3u8 = stream["hls_pull_url_map"][resolution] 163 | 164 | ext = "flv" 165 | playback = "m3u8" 166 | return self.compact() 167 | 168 | def getSign(self, nonce): 169 | js = self.read(self.abspath + "/tool/javascript/douyin.sign.js") 170 | try: 171 | ctx = self.modules["execjs"].compile(js) 172 | sign = ctx.call("get_sign", nonce) 173 | except: 174 | sign = "_02B4Z6wo00f011STaVgAAIDC0rtM.EB46GtUom3AALaN5qKTFC4qCqg-fvNhWfGihyk9zChBBYW39vA2w5aTK14w48Hspeg-fFmRHS3.KMjtyBBTNms0LNJ-dqHdw-e6dy08s1j8YLPSsWx479" 175 | return sign 176 | -------------------------------------------------------------------------------- /qito/parse/live/douyu.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : douyu.py 5 | @Time : 2022/11/30 上午12:24 6 | """ 7 | import template 8 | 9 | 10 | class Main(template.Template): 11 | def __init__(self): 12 | super().__init__() 13 | self.title = "斗鱼直播(DOUYU)" 14 | self.require = [("quickjs", "Function", "fn"), "execjs", "importlib"] 15 | 16 | def query(self): 17 | p = self.params 18 | if self.hasurl(p["parse"]): 19 | cid = self.match("\/(\d+)", p["parse"]) 20 | else: 21 | cid = p["parse"] 22 | 23 | if cid: 24 | html = self.curl( 25 | {"url": f"https://www.douyu.com/{cid}", "encoding": "utf8"} 26 | ) 27 | else: 28 | html = self.curl(p["parse"]) 29 | vid = self.match( 30 | [ 31 | "ROOM.room_id\s*=\s*(\d+)", 32 | "room_id\s*=\s*(\d+)", 33 | '"room_id.?":(\d+)', 34 | "data-onlineid=(\d+)", 35 | ], 36 | html, 37 | ) 38 | title = self.match('Title-headlineH2">([^<]+)<', html) 39 | anchor = self.match('Title-anchorName" title="([^"]+)"', html) 40 | 41 | if not anchor or not title: 42 | url = f"https://open.douyucdn.cn/api/RoomApi/room/{vid}" 43 | content = self.curl({"url": url, "encoding": "utf8"}) 44 | 45 | self.logging.debug(f"getInfo: {content} \r\n") 46 | json = self.loads(content) 47 | 48 | if json["error"] == 0: 49 | anchor = json["data"]["owner_name"] 50 | title = json["data"]["room_name"].replace("\t", "") 51 | image = json["data"]["room_thumb"] 52 | 53 | return self.compact() 54 | 55 | def parse(self): 56 | p = self.params 57 | assert p["vid"], "vid" 58 | vid = p["vid"] 59 | timestamp = str(self.time) 60 | 61 | crypto = self.read(f"{self.abspath}/tool/javascript/crypto-js.min.js") 62 | url = f"https://www.douyu.com/swf_api/homeH5Enc?rids={vid}" 63 | get_enc = self.curl(url) 64 | json = self.loads(get_enc) 65 | assert json.get("error") == 0, "homeH5enc" 66 | jsEnc = json["data"][f"room{vid}"] 67 | replaceString = """ 68 | var newString = "'" + Ee + "';"; 69 | var evalString = eval(newString); 70 | evalString = evalString.replace(/(.*oog\(.*)/, "$1oog['test'] = function(a) { return 1; }"); 71 | eval(evalString); 72 | """ 73 | jsDom = jsEnc.replace("eval(Ee);", replaceString) 74 | 75 | dom = "let window = {},document = {};" 76 | 77 | try: 78 | f = self.modules["fn"]("ub98484234", f"{crypto};{dom};{jsDom};") 79 | ub98484234 = f(vid, self.md5(timestamp), timestamp) 80 | 81 | except: 82 | ctx = self.modules["execjs"].compile(f"{crypto};{dom};{jsDom};") 83 | ub98484234 = ctx.call("ub98484234", vid, self.md5(timestamp), timestamp) 84 | 85 | self.logging.debug(f"ub98484234: {ub98484234} \r\n") 86 | h5Url = f"https://www.douyu.com/lapi/live/getH5Play/{vid}" 87 | rateTemp = 0 88 | postData = self.qsl( 89 | f"{ub98484234}&cdn=&rate={rateTemp}&ver=Douyu_219042402&iar=1&ive=0" 90 | ) 91 | 92 | source = self.curl( 93 | { 94 | "url": f"https://www.douyu.com/lapi/live/getH5Play/{vid}", 95 | "method": "post", 96 | "form": postData, 97 | "encoding": "utf8", 98 | } 99 | ) 100 | self.logging.debug(f"getLive: {source} \r\n") 101 | playJson = self.loads(source) 102 | if playJson["error"] == 0: 103 | multirates = self.column(playJson["data"]["multirates"], "", "name") 104 | quality = list(multirates.keys())[::-1] 105 | show = self.data(quality, p["hd"]) 106 | rate = multirates[show]["rate"] 107 | if rateTemp != rate: 108 | postData = self.qsl( 109 | f"{ub98484234}&cdn=&rate={rateTemp}&ver=Douyu_219042402&iar=1&ive=0" 110 | ) 111 | 112 | request = {"method": "post", "form": postData, "encoding": "utf8"} 113 | source = self.curl(h5Url, request) 114 | playJson = self.loads(source) 115 | 116 | flv = f"{playJson['data']['rtmp_url']}/{playJson['data']['rtmp_live']}" 117 | ext = playback = "flv" 118 | 119 | # 获取M3U8 120 | rateUrl = "https://m.douyu.com/api/room/ratestream" 121 | rateDict = self.qsl(f"{ub98484234}&ver=219032101&rid={vid}&rate={rate}") 122 | 123 | request = {"method": "post", "form": rateDict, "decoding": "utf8"} 124 | 125 | rateSource = self.curl(rateUrl, request) 126 | self.logging.debug(f"ratestream: {rateSource} \r\n") 127 | streamDict = self.loads(rateSource) 128 | if streamDict["code"] == 0: 129 | m3u8 = streamDict["data"]["url"] 130 | playback = "m3u8" 131 | 132 | elif playJson["error"] == -5: 133 | vod_url = f"https://www.douyu.com/swf_api/getRoomCloseVod/{vid}" 134 | 135 | get_vod = self.curl(vod_url) 136 | self.logging.debug(f"getRoomCloseVod: {get_vod} \r\n") 137 | roomJson = self.loads(get_vod) 138 | info = roomJson.get("lastVidInfo") 139 | 140 | if info: 141 | self.params["category"] = "video" 142 | vid = info["vid"] 143 | aid = self.match("show\/(\w+)", info["url"]) 144 | imp = self.modules["importlib"].import_module("parse.video.douyu") 145 | a = imp.Main() 146 | a.modules = self.modules 147 | a.params = {"vid": vid, "hd": p["hd"], "aid": aid} 148 | return a.parse() 149 | extra = {"remove": 1} 150 | return self.compact() 151 | -------------------------------------------------------------------------------- /qito/parse/live/huajiao.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : huajiao.py 5 | @Time : 2022/12/1 下午9:44 6 | """ 7 | import template 8 | 9 | 10 | class Main(template.Template): 11 | def __init__(self): 12 | super().__init__() 13 | self.title = "花椒直播(HUAJIAO)" 14 | 15 | def query(self): 16 | p = self.params 17 | if p["parse"].startswith("http"): 18 | vid = self.match("\/(\d+)", p["parse"]) 19 | else: 20 | vid = p["parse"] 21 | if p.get("query"): 22 | url = f"https://www.huajiao.com/l/{vid}" 23 | turl = self.curl(url, {"response": "location"}) 24 | assert turl != "https://www.huajiao.com/", "close" 25 | html = self.curl(url) 26 | keywords = self.match('name="keywords" content="([^"]+)"', html) 27 | spl = keywords.split(",") 28 | title = spl[0] 29 | anchor = spl[1] 30 | 31 | return self.compact() 32 | 33 | def parse(self): 34 | p = self.params 35 | assert p["vid"], "vid" 36 | vid = p["vid"] 37 | 38 | url = f"https://www.huajiao.com/l/{vid}" 39 | turl = self.curl(url, {"response": "location"}) 40 | assert turl != "https://www.huajiao.com/", "close" 41 | html = self.curl(url) 42 | keywords = self.match('name="keywords" content="([^"]+)"', html) 43 | spl = keywords.split(",") 44 | title = spl[0] 45 | anchor = spl[1] 46 | sn = self.match('"sn":"([^"]+)"', html) 47 | uid = self.match('"uid":"(\d+)"', html) 48 | 49 | sUrl = f"https://live.huajiao.com/live/substream?guid=aff3a83c325ea1db22262bc87361b590&platform=android&rand=0.8804918002581339&time={ self.timestamp}&version=1.0.0&sn={sn}&uid={uid}&liveid={vid}&encode=h265" 50 | substream = self.curl(sUrl) 51 | self.logging.debug(f"getLive: {substream} \r\n") 52 | json = self.loads(substream) 53 | assert json["errno"] == 0, "data" 54 | flv = json["data"]["h264_url"] 55 | ext = playback = "flv" 56 | return self.compact() 57 | -------------------------------------------------------------------------------- /qito/parse/live/huya.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : huya.py 5 | @Time : 2022/11/29 下午11:04 6 | """ 7 | import template 8 | 9 | 10 | class Main(template.Template): 11 | def __init__(self): 12 | super().__init__() 13 | self.title = "虎牙直播(HUYA)" 14 | 15 | def query(self): 16 | p = self.params 17 | if p["parse"].startswith("http"): 18 | vid = self.match("com\/(\w+)", p["parse"]) 19 | else: 20 | vid = p["parse"] 21 | if p.get("query"): 22 | url = f"https://www.huya.com/{vid}" 23 | html = self.curl(url) 24 | b64 = self.match(r"stream: ({.+)\n.*?};", html) 25 | json = self.jsonParse(b64) 26 | data = json["data"][0] 27 | anchor = data["gameLiveInfo"]["nick"] 28 | title = data["gameLiveInfo"]["introduction"] 29 | return self.compact() 30 | 31 | def parse(self): 32 | p = self.params 33 | assert p["vid"], "vid" 34 | vid = p["vid"] 35 | url = f"https://www.huya.com/{vid}" 36 | html = self.curl(url) 37 | b64 = self.match(r"stream: ({.+)\n.*?};", html) 38 | 39 | assert b64, "close" 40 | self.logging.debug(f"getStream: {b64} \r\n") 41 | json = self.jsonParse(b64) 42 | data = json["data"][0] 43 | vMultiStreamInfo = json["vMultiStreamInfo"] 44 | dicts = {i["sDisplayName"]: i["iBitRate"] for i in vMultiStreamInfo} 45 | quality = list(dicts.keys())[::-1] 46 | show = self.data(quality, p["hd"]) 47 | ratio = dicts[show] 48 | anchor = data["gameLiveInfo"]["nick"] 49 | title = data["gameLiveInfo"]["introduction"] 50 | 51 | lists = data["gameStreamInfoList"][0] 52 | 53 | m3u8 = "{}/{}.{}?{}&ratio={}".format( 54 | lists["sHlsUrl"], 55 | lists["sStreamName"], 56 | lists["sHlsUrlSuffix"], 57 | lists["sHlsAntiCode"].replace("&", "&"), 58 | ratio, 59 | ) 60 | flv = "{}/{}.{}?{}&ratio={}".format( 61 | lists["sFlvUrl"], 62 | lists["sStreamName"], 63 | lists["sFlvUrlSuffix"], 64 | lists["sFlvAntiCode"].replace("&", "&"), 65 | ratio, 66 | ) 67 | playback = "m3u8" 68 | ext = "flv" 69 | 70 | return self.compact() 71 | -------------------------------------------------------------------------------- /qito/parse/live/pps.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : pps.py 5 | @Time : 2022/12/1 下午3:26 6 | """ 7 | import template 8 | 9 | 10 | class Main(template.Template): 11 | def __init__(self): 12 | super().__init__() 13 | self.title = "奇秀直播(PPS)" 14 | 15 | def query(self): 16 | p = self.params 17 | if "gamelive.iqiyi.com" in p["parse"]: 18 | p["parse"] = self.curl(p["parse"], {"response": "location"}) 19 | 20 | if self.hasurl(p["parse"]): 21 | 22 | vid = self.match("\/(\d+)", p["parse"]) 23 | else: 24 | vid = p["parse"] 25 | 26 | url = f"https://x.pps.tv/room/{vid}" 27 | 28 | html = self.curl(url) 29 | uid = self.match(['"user_id"\s*:\s*"(\d+)"', "user_id=(\d+)"], html) 30 | title = self.loads(self.match('"room_name":("[^"]*"),', html)) 31 | roomConfig = self.match("_room_config\s*=\s*([^;]+)", html) 32 | self.logging.debug(f"getRoomConfig: {roomConfig} \r\n") 33 | try: 34 | json = self.loads(roomConfig) 35 | anchor = json["anchor_info"]["nick_name"] 36 | title = json["live_info"]["live_title"] 37 | image = json["live_info"]["live_image"] 38 | except: 39 | pass 40 | 41 | return self.compact() 42 | 43 | def parse(self): 44 | p = self.params 45 | assert p["vid"], "vid" 46 | vid = p["vid"] 47 | timestamp = self.time 48 | assert p["uid"], "uid" 49 | uid = p["uid"] 50 | url = "https://m-glider-xiu.pps.tv/v2/stream/get.json" 51 | ary = { 52 | "smooth": "标清", 53 | "high": "高清", 54 | "source": "超清", 55 | } 56 | quality = list(ary.values()) 57 | show = self.data(quality, p["hd"]) 58 | rate = self.data(ary.keys(), p["hd"]) 59 | 60 | postData = { 61 | "anchor_id": uid, 62 | "app_key": "show_web_h5", 63 | "type_id": "1", 64 | "vid": "1", 65 | "version": "1.0.0", 66 | "platform": "1_10_101", 67 | "time": timestamp, 68 | "netstat": "wifi", 69 | "device_id": self.md5(uid), 70 | "bit_rate_type": rate, 71 | "protocol": "5", 72 | } 73 | postData["sign"] = self.getSign(postData) 74 | html = self.curl(url, {"method": "post", "form": postData}) 75 | self.logging.debug(f"getJson: {html} \r\n") 76 | json = self.loads(html) 77 | 78 | assert json["code"] == "A00000", "close" 79 | flv = json["data"]["https_flv"] 80 | playback = ext = "flv" 81 | return self.compact() 82 | 83 | def getSign(self, params): 84 | # string=self.self.urlencode(params)+"w!ytDgy#lEXWoJmN4HPf" 85 | s = [] 86 | for key in sorted(params.keys()): 87 | s.append(f"{key}:{params[key]}") 88 | s.append("w!ytDgy#lEXWoJmN4HPf") 89 | s = "".join(s) 90 | return self.sha1(s) 91 | -------------------------------------------------------------------------------- /qito/parse/music/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : __init__.py.py 5 | @Time : 2022/10/8 上午10:49 6 | """ 7 | -------------------------------------------------------------------------------- /qito/parse/music/qq.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : qq.py 5 | @Time : 2022/10/8 上午10:49 6 | """ 7 | import template 8 | 9 | 10 | class Main(template.Template): 11 | def __init__(self): 12 | super().__init__() 13 | self.title = "腾讯音乐(QQ)" 14 | 15 | def query(self): 16 | p = self.params 17 | if p["parse"].startswith("http"): 18 | if "songid" in p["parse"]: 19 | vid = self.match(r"songid=(\w+)", p["parse"]) 20 | elif "song/" in p["parse"]: 21 | vid = self.match(r"song\/(\w+)", p["parse"]) 22 | elif "songDetail" in p["parse"]: 23 | vid = self.match(r"songDetail\/(\w+)", p["parse"]) 24 | 25 | else: 26 | vid = p["parse"] 27 | mParams = { 28 | "hostUin": "0", 29 | "format": "jsonp", 30 | "inCharset": "utf8", 31 | "outCharset": "utf-8", 32 | "notice": "0", 33 | "platform": "yqq", 34 | "needNewCode": "0", 35 | "data": '{"comm":{"ct":24,"cv":0},"songinfo":{"method":"get_song_detail_yqq","param":{"song_type":0,"song_mid":"%s"},"module":"music.pf_song_detail_svr"}}' 36 | % vid, 37 | } 38 | url = "https://u.y.qq.com/cgi-bin/musicu.fcg?{}".format(self.urlencode(mParams)) 39 | html = self.curl(url) 40 | self.logging.debug(f"getInfo: {html} \r\n") 41 | json = self.loads(html) 42 | assert json["songinfo"]["data"], "data" 43 | data = json["songinfo"]["data"] 44 | 45 | company = self.haskey(data, "info.company.content.0.value") 46 | genry = self.haskey(data, "info.genre.content.0.value") 47 | 48 | album = data["track_info"]["album"]["title"].strip() 49 | duration = data["track_info"]["interval"] 50 | cover = data["track_info"]["album"]["mid"] 51 | image = f"https://y.gtimg.cn/music/photo_new/T002R300x300M000{cover}.jpg" 52 | title = data["track_info"]["title"].strip() 53 | cid = data["track_info"]["id"] 54 | publish = data["track_info"]["time_public"] 55 | language = data["info"]["lan"]["content"][0]["value"].strip() 56 | singer = " | ".join([i["title"].strip() for i in data["track_info"]["singer"]]) 57 | return self.compact() 58 | 59 | def parse(self): 60 | p = self.params 61 | vid = p["vid"] 62 | params = { 63 | "hostUin": "0", 64 | "format": "jsonp", 65 | "inCharset": "utf8", 66 | "outCharset": "utf-8", 67 | "notice": "0", 68 | "platform": "yqq", 69 | "needNewCode": "0", 70 | "data": '{"req":{"module":"CDN.SrfCdnDispatchServer","method":"GetCdnDispatch","param":{"guid":"7152021848","calltype":0,"userip":""}},"req_0":{"module":"vkey.GetVkeyServer","method":"CgiGetVkey","param":{"guid":"7152021848","songmid":["%s"],"songtype":[0],"uin":"","loginflag":1,"platform":"20"}},"comm":{"uin":"","format":"json","ct":24,"cv":0}}' 71 | % vid, 72 | } 73 | url = "https://u.y.qq.com/cgi-bin/musicu.fcg?{}".format(self.urlencode(params)) 74 | html = self.curl(url) 75 | self.logging.debug(f"getSong: {html} \r\n") 76 | json = self.jsonParse(html) 77 | try: 78 | m4a = ( 79 | "http://isure.stream.qqmusic.qq.com/" 80 | + json["req_0"]["data"]["midurlinfo"][0]["purl"] 81 | ) 82 | # print(m4a) 83 | playback = ext = "m4a" 84 | except: 85 | raise NotImplementedError("data") 86 | 87 | return self.compact() 88 | -------------------------------------------------------------------------------- /qito/parse/playlist/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : __init__.py.py 5 | @Time : 2022/8/28 上午10:50 6 | """ 7 | -------------------------------------------------------------------------------- /qito/parse/playlist/bilibili.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : bilibili.py 5 | @Time : 2022/10/12 下午7:42 6 | """ 7 | import template 8 | 9 | 10 | class Main(template.Template): 11 | def __init__(self): 12 | super().__init__() 13 | self.title = "哔哩哔哩剧集列表" 14 | 15 | def videoList(self, params): 16 | serial = "" 17 | url = params["parse"] 18 | lists = [] 19 | if "space.bilibili.com" in url: 20 | uid = self.match("space.bilibili.com\/(\d+)", url) 21 | for i in range(1000): 22 | page = i + 1 23 | u = f"https://api.bilibili.com/x/space/arc/search?mid={uid}&ps=30&tid=0&pn={page}&keyword=&order=pubdate" 24 | html = self.curl(u) 25 | json = self.loads(html) 26 | if json["code"] != 0: 27 | break 28 | count = json["data"]["page"]["count"] 29 | y = count / 30 30 | vlist = self.column(json["data"]["list"]["vlist"], "aid") 31 | lists.extend(vlist) 32 | if page > y: 33 | break 34 | elif "/md" in url: 35 | content = self.curl(url) 36 | seasonId = self.match('"season_id":(\d+)', content) or self.match( 37 | "\/md(\d+)", url 38 | ) 39 | seasonUrl = ( 40 | f"https://api.bilibili.com/pgc/web/season/section?season_id={seasonId}" 41 | ) 42 | 43 | html = self.curl(seasonUrl) 44 | json = self.loads(html) 45 | assert json["code"] == 0, "data" 46 | lists = self.column(json["result"]["main_section"]["episodes"], "aid") 47 | 48 | elif "/av" in url: 49 | aid = self.match("\/av(\d+)", url) 50 | pageUrl = ( 51 | f"https://api.bilibili.com/x/player/pagelist?aid={aid}&jsonp=jsonp" 52 | ) 53 | html = self.curl(pageUrl) 54 | json = self.loads(html) 55 | assert json["code"] == 0, "data" 56 | lists = [ 57 | f"https://www.bilibili.com/video/av{aid}?p={i + 1}" 58 | for i in range(len(json["data"])) 59 | ] 60 | elif "/BV" in url: 61 | bvid = self.match("\/(BV\w+)", url) 62 | aid = self.dec(bvid) 63 | pageUrl = ( 64 | f"https://api.bilibili.com/x/player/pagelist?aid={aid}&jsonp=jsonp" 65 | ) 66 | html = self.curl(pageUrl) 67 | json = self.loads(html) 68 | assert json["code"] == 0, "data" 69 | lists = [ 70 | f"https://www.bilibili.com/video/av{aid}?p={i + 1}" 71 | for i in range(len(json["data"])) 72 | ] 73 | else: 74 | html = self.curl(url) 75 | data = self.match("window.__INITIAL_STATE__=(\{.*?\});", html) 76 | json = self.loads(data) 77 | serial = self.haskey(json, "mediaInfo.title") 78 | lists = [ 79 | { 80 | "parse": str(i["cid"]), 81 | "title": "%s %s" % (i["titleFormat"], i["longTitle"]), 82 | } 83 | for i in json["epList"] 84 | ] 85 | 86 | return { 87 | "data": lists, 88 | "category": "video", 89 | "type": "bilibili", 90 | "serial": serial, 91 | } 92 | 93 | def dec(self, x): 94 | # https://www.zhihu.com/question/381784377/answer/1099438784 95 | tr = { 96 | "f": 0, 97 | "Z": 1, 98 | "o": 2, 99 | "d": 3, 100 | "R": 4, 101 | "9": 5, 102 | "X": 6, 103 | "Q": 7, 104 | "D": 8, 105 | "S": 9, 106 | "U": 10, 107 | "m": 11, 108 | "2": 12, 109 | "1": 13, 110 | "y": 14, 111 | "C": 15, 112 | "k": 16, 113 | "r": 17, 114 | "6": 18, 115 | "z": 19, 116 | "B": 20, 117 | "q": 21, 118 | "i": 22, 119 | "v": 23, 120 | "e": 24, 121 | "Y": 25, 122 | "a": 26, 123 | "h": 27, 124 | "8": 28, 125 | "b": 29, 126 | "t": 30, 127 | "4": 31, 128 | "x": 32, 129 | "s": 33, 130 | "W": 34, 131 | "p": 35, 132 | "H": 36, 133 | "n": 37, 134 | "J": 38, 135 | "E": 39, 136 | "7": 40, 137 | "j": 41, 138 | "L": 42, 139 | "5": 43, 140 | "V": 44, 141 | "G": 45, 142 | "3": 46, 143 | "g": 47, 144 | "u": 48, 145 | "M": 49, 146 | "T": 50, 147 | "K": 51, 148 | "N": 52, 149 | "P": 53, 150 | "A": 54, 151 | "w": 55, 152 | "c": 56, 153 | "F": 57, 154 | } 155 | s = [11, 10, 3, 8, 4, 6] 156 | xor = 177451812 157 | add = 8728348608 158 | r = 0 159 | for i in range(6): 160 | r += tr[x[s[i]]] * 58**i 161 | return (r - add) ^ xor 162 | 163 | def enc(self, x): 164 | table = "fZodR9XQDSUm21yCkr6zBqiveYah8bt4xsWpHnJE7jL5VG3guMTKNPAwcF" 165 | s = [11, 10, 3, 8, 4, 6] 166 | xor = 177451812 167 | add = 8728348608 168 | x = (int(x) ^ xor) + add 169 | r = list("BV1 4 1 7 ") 170 | for i in range(6): 171 | r[s[i]] = table[x // 58**i % 58] 172 | return "".join(r) 173 | -------------------------------------------------------------------------------- /qito/parse/playlist/ixigua.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : ixigua.py 5 | @Time : 2022/10/14 下午6:40 6 | """ 7 | import template 8 | 9 | 10 | class Main(template.Template): 11 | def __init__(self): 12 | super().__init__() 13 | self.title = "西瓜视频剧集列表" 14 | 15 | def videoList(self, params): 16 | if params["parse"].startswith("http"): 17 | cover = self.match("\/(\d+)", params["parse"]) 18 | else: 19 | cover = params["parse"] 20 | json = self.curl( 21 | { 22 | "url": "https://www.ixigua.com/api/albumv2/details", 23 | "params": {"albumId": cover}, 24 | "referer": "https://www.ixigua.com/", 25 | "response": "json", 26 | } 27 | ) 28 | assert json["code"] == 200, "playlist" 29 | 30 | lists = [ 31 | f'https://www.ixigua.com/{i["albumId"]}?id={i["episodeId"]}' 32 | for i in json["data"]["playlist"] 33 | ] 34 | serial = self.haskey(json, "data.albumInfo.title") 35 | return {"data": lists, "category": "video", "type": "ixigua", "serial": serial} 36 | -------------------------------------------------------------------------------- /qito/parse/playlist/le.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : le.py 5 | @Time : 2022/8/28 上午10:51 6 | """ 7 | import template 8 | 9 | 10 | class Main(template.Template): 11 | def __init__(self): 12 | super().__init__() 13 | self.title = "乐视剧集列表" 14 | 15 | def videoList(self, params): 16 | if params["parse"].startswith("http"): 17 | vid = self.match("\/vplay\/(\d+)", params["parse"]) 18 | if not vid: 19 | cover = self.match("\/(\d+)", params["parse"]) 20 | else: 21 | vid = params["parse"] 22 | serial = "" 23 | if vid: 24 | tvUrl = f"http://www.le.com/ptv/vplay/{vid}.html" 25 | tvSource = self.curl( 26 | { 27 | "url": tvUrl, 28 | # 'from':'', 29 | } 30 | ) 31 | cover = self.match("pid\s*:\s*(\d+)", tvSource) 32 | serial = self.match('pTitle\s*:\s*"([^"]+)"', tvSource) 33 | 34 | url = f"http://d.api.m.le.com/detail/episode?pid={cover}&platform=pc&page=1&pagesize=2000&type=1&_=1565045426195" 35 | 36 | html = self.curl(url) 37 | json = self.loads(html) 38 | assert json["code"] == "200", "playlist" 39 | 40 | lists = [i["vid"] for i in json["data"]["list"]] 41 | if len(lists) and not serial: 42 | tvUrl = f"http://www.le.com/ptv/vplay/{lists[0]}.html" 43 | tvSource = self.curl( 44 | { 45 | "url": tvUrl, 46 | # 'from':'', 47 | } 48 | ) 49 | serial = self.match('pTitle\s*:\s*"([^"]+)"', tvSource) 50 | 51 | return {"data": lists, "category": "video", "type": "le", "serial": serial} 52 | -------------------------------------------------------------------------------- /qito/parse/playlist/miguvideo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : miguvideo.py 5 | @Time : 2022/11/28 下午2:05 6 | """ 7 | import template 8 | 9 | 10 | class Main(template.Template): 11 | def __init__(self): 12 | super().__init__() 13 | self.title = "咪咕视频列表" 14 | 15 | def videoList(self, params): 16 | if params["parse"].startswith("http"): 17 | cover = self.match("=(\d+)", params["parse"]) 18 | else: 19 | cover = params["parse"] 20 | url = f"https://program-sc.miguvideo.com/program/v3/cont/content-info/{cover}/1" 21 | html = self.curl(url) 22 | json = self.loads(html) 23 | 24 | lists = [ 25 | {"title": i["name"], "parse": i["pID"]} 26 | for i in self.haskey(json, "body.data.datas") 27 | ] 28 | serial = self.haskey(json, "body.data.name") 29 | return { 30 | "data": lists, 31 | "category": "video", 32 | "type": "miguvideo", 33 | "serial": serial, 34 | } 35 | -------------------------------------------------------------------------------- /qito/parse/playlist/qq.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : qq.py 5 | @Time : 2022/9/8 下午10:51 6 | """ 7 | import template 8 | 9 | 10 | class Main(template.Template): 11 | def __init__(self): 12 | super().__init__() 13 | self.title = "腾讯剧集列表" 14 | 15 | def videoList(self, params): 16 | cover = "" 17 | if params["parse"].startswith("http"): 18 | cover = self.match("cover\/(\w+)", params["parse"]) 19 | vid = self.match( 20 | ["vid=(\w+)", "/(\w+)$", "cover\/\w+\/(\w+)"], 21 | params["parse"], 22 | ) 23 | html = self.curl(params["parse"]) 24 | if not vid: 25 | vid = self.match( 26 | [ 27 | "&vid=(\w+)", 28 | "vid:\s*[\"'](\w+)", 29 | "vid\s*=\s*[\"']\s*(\w+)", 30 | '"vid":"(\w+)"', 31 | ], 32 | html, 33 | ) 34 | else: 35 | vid = params["parse"] 36 | if vid and not cover: 37 | u = f"https://m.v.qq.com/play.html?cid=&vid={vid}&ptag=v_qq_com%23v.play.adaptor%233" 38 | h = self.curl( 39 | { 40 | "url": u, 41 | } 42 | ) 43 | cover = self.match(["cid%22%3A%22(\w+)", "cid\s*=\s*'(\w+)'"], h) 44 | 45 | s = self.curl( 46 | { 47 | "url": f"https://node.video.qq.com/x/api/float_vinfo2?cid={cover}", 48 | # 'from':'', 49 | } 50 | ) 51 | d = self.jsonParse(s) 52 | serial = d["c"]["title"] 53 | playList = d["c"]["video_ids"] 54 | return {"data": playList, "category": "video", "type": "qq", "serial": serial} 55 | -------------------------------------------------------------------------------- /qito/parse/playlist/sohu.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : sohu.py 5 | @Time : 2022/12/6 上午10:58 6 | """ 7 | import template 8 | 9 | 10 | class Main(template.Template): 11 | def __init__(self): 12 | super().__init__() 13 | self.title = "搜狐视频剧集(SOHU)" 14 | 15 | def videoList(self, params): 16 | url = params["parse"] 17 | 18 | if url.startswith("http"): 19 | vid = self.match("v\/([^.]+)", url) 20 | else: 21 | vid = url 22 | url = f"https://tv.sohu.com/v/{vid}.html" 23 | html = self.curl( 24 | { 25 | "url": url, 26 | # 'from':'', 27 | } 28 | ) 29 | 30 | playlistId = self.match(r'playlistId\s*=\s*"(\d+)"', html) 31 | a = f"https://pl.hd.sohu.com/videolist?playlistid={playlistId}&o_playlistId=&pianhua=0&pagenum=1&pagesize=300&order=0&cnt=1&pageRule=2&withPgcVideo=1&withLookPoint=1&ssl=0&preVideoRule=3&_=1670295414041" 32 | b = self.curl( 33 | { 34 | "url": a, 35 | # 'from':'', 36 | } 37 | ) 38 | c = self.jsonParse(b) 39 | playlist = [f"https:{i['pageUrl']}" for i in c["videos"]] 40 | serial = self.haskey(c, "albumName") 41 | return {"data": playlist, "category": "video", "type": "sohu", "serial": serial} 42 | -------------------------------------------------------------------------------- /qito/parse/playlist/youku.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : youku.py 5 | @Time : 2022/9/4 下午5:01 6 | """ 7 | import template 8 | 9 | 10 | class Main(template.Template): 11 | def __init__(self): 12 | super().__init__() 13 | self.title = "优酷剧集列表" 14 | 15 | def videoList(self, params): 16 | url = params["parse"] 17 | if url.startswith("http"): 18 | url = self.curl({"url": url, "response": "location"}) 19 | vid = self.match("v_show/id_([^\.]+)", url) 20 | else: 21 | vid = url 22 | if vid: 23 | ccode = "0524" 24 | try: 25 | utid = self.curl( 26 | { 27 | "url": "http://log.mmstat.com/eg.js", 28 | "headers": { 29 | "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.16; rv:84.0) Gecko/20100101 Firefox/84.0", 30 | "Referer": "https://g.alicdn.com/alilog/oneplus/blk.html", 31 | }, 32 | "response": "cookie", 33 | }, 34 | )["cna"] 35 | except: 36 | utid = "qyiUGD8MHWkCARudQQu7gaFM" 37 | params = { 38 | "vid": vid, 39 | "ccode": ccode, 40 | "client_ip": "192.168.1.1", 41 | "utid": utid, 42 | "client_ts": self.timestamp, 43 | "ckey": "115#1dCil11O1TaNcv6zGfND1Cso311GLyAi1/2Wsi/gdl1it86z19WZy56NE8P1IvvCtU/8yPZQi/WJ1aU4AWNcaLBfOZPQOSAPetT4yWZQgbvJhEz4vBN4+5yAurrQ/jfyet/4yWZQiQ+ghZz8OWNcaTpjurPdvOoNPKxRLp+2i7lL1FGYdPYMTT9cxCNRlGg0kxHucjHqect+1pQNBWMKLRoQD1ZDrSxQ+F6RY5gk0Bmywzgc8WLIgwd2piFbn1q6EWcfZc1Wg5bS6ancr86G4xLB9BSOV9noSrKFv5r4lzKO8RWkfVdXHmIATO1SsV5RtVQCAcMDKsROmJbQy8ozjXN+EK4p3CZSxTcnDRaAr/TrOZSCIXoXN8k3EkUCh1QV3dpRqjRzsM4qBS36s8JOXl9VM4+QJ0+DLRJUKP3J11CNo0odneVsS52H58CYeEqN", 44 | } 45 | html = self.curl( 46 | { 47 | "url": "https://ups.youku.com/ups/get.json", 48 | "params": params, 49 | } 50 | ) 51 | json = self.jsonParse(html) 52 | 53 | serial = self.haskey(json, "data.show.title") 54 | cover = self.haskey(json, "data.show.encodeid") 55 | if cover: 56 | s = self.curl( 57 | { 58 | "url": f"https://search.youku.com/api/search?appScene=show_episode&showIds={cover}&appCaller=h5" 59 | } 60 | ) 61 | data = self.loads(s) 62 | playlist = self.column(data["serisesList"], "videoId") 63 | return { 64 | "data": playlist, 65 | "category": "video", 66 | "type": "youku", 67 | "serial": serial, 68 | } 69 | -------------------------------------------------------------------------------- /qito/parse/video/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : __init__.py.py 5 | @Time : 2021/10/19 下午5:47 6 | """ 7 | -------------------------------------------------------------------------------- /qito/parse/video/acfun.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : acfun.py 5 | @Time : 2022/11/29 下午1:59 6 | """ 7 | import template 8 | 9 | 10 | class Main(template.Template): 11 | def __init__(self): 12 | super().__init__() 13 | self.title = "()" 14 | 15 | def query(self): 16 | p = self.params 17 | if not p["parse"].startswith("http"): 18 | if self.match("aa(\d+)", p["parse"]): 19 | p["parse"] = "https://www.acfun.cn/bangumi/%s" % p["parse"] 20 | elif self.match("ac(\d+)", p["parse"]): 21 | p["parse"] = "https://www.acfun.cn/v/%s" % p["parse"] 22 | 23 | if p["parse"].startswith("http"): 24 | aid = self.match(["ac(\d+)", "ac=(\d+)"], p["parse"]) 25 | 26 | if not aid: 27 | otype = "bangumi" 28 | html = self.curl({"url": p["parse"], "encoding": "utf8"}) 29 | 30 | vid = self.match(['"videoId":(\d+),', '"danmakuId":(\d+)'], html) 31 | image = self.match('"image":"([^"]+)",', html) 32 | title = self.match('"episodeName":"(.*?)"', html) 33 | 34 | else: 35 | otype = "video" 36 | html = self.curl({"url": p["parse"], "encoding": "utf8"}) 37 | vid = self.match('"currentVideoId":(\d+),', html) 38 | title = self.match( 39 | ["\(.*?)\s*-\s*AcFun", '"title":"([^"]+)",'], html 40 | ) 41 | image = self.match('"url":"(https:\/\/imgs.+?)",', html) 42 | 43 | else: 44 | vid = p["parse"] 45 | 46 | return self.compact() 47 | 48 | def parse(self): 49 | p = self.params 50 | assert p["vid"], "vid" 51 | vid = p["vid"] 52 | aid = self.haskey(p, "aid") 53 | m3u8 = "" 54 | if p["parse"].startswith("http"): 55 | content = self.curl(p["parse"]) 56 | videoInfo = self.match( 57 | [ 58 | "window.pageInfo\s*=(?:\s*window.videoInfo\s*=\s*)*(?:\s*window.bangumiData\s*=)*\s*(.*);\s*", 59 | "window.pageInfo\s*=(?:\s*window.videoInfo\s*=\s*)*(?:\s*window.bangumiData\s*=)*\s*([^;]+)", 60 | ], 61 | content, 62 | ) 63 | self.logging.debug(f"videoInfo: {videoInfo} \r\n") 64 | try: 65 | videoJson = self.loads(videoInfo) 66 | title = videoJson["title"] 67 | 68 | duration = videoJson["currentVideoInfo"]["durationMillis"] / 1000 69 | image = self.haskey(videoJson, "coverCdnUrls.0.url") 70 | 71 | try: 72 | representation = self.loads( 73 | videoJson["currentVideoInfo"]["ksPlayJson"] 74 | )["adaptationSet"][0]["representation"] 75 | except: 76 | representation = self.loads( 77 | videoJson["currentVideoInfo"]["ksPlayJson"] 78 | )["adaptationSet"]["representation"] 79 | 80 | try: 81 | 82 | m3u8List = self.column(representation, "url", "qualityLabel") 83 | except: 84 | 85 | lists = self.column(representation, "url", "bandwidth") 86 | 87 | def label(bandwidth): 88 | if bandwidth > 3000000: 89 | q = "1080p" 90 | elif bandwidth > 1500000: 91 | q = "超清" 92 | elif bandwidth > 900000: 93 | q = "高清" 94 | elif bandwidth > 400000: 95 | q = "标清" 96 | else: 97 | q = "自动" 98 | return q 99 | 100 | m3u8List = {label(int(i)): lists[i] for i in lists} 101 | # 102 | 103 | quality = list(m3u8List.keys()) 104 | 105 | ary = [ 106 | "自动", 107 | "360P", 108 | "标清", 109 | "540P", 110 | "高清", 111 | "720P", 112 | "720P60", 113 | "超清", 114 | "1080P", 115 | "1080P+", 116 | "1080P60", 117 | "2160P", 118 | "2160P60", 119 | ] 120 | try: 121 | quality.sort(key=ary.index) 122 | except: 123 | quality = quality.reverse() 124 | 125 | show = self.data(quality, p["hd"]) 126 | m3u8 = m3u8List[show] 127 | segs = [{"url": m3u8}] 128 | ext = "m3u8" 129 | playback = "m3u8" 130 | except: 131 | pass 132 | return self.compact() 133 | -------------------------------------------------------------------------------- /qito/parse/video/bilibili.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : bilibili.py 5 | @Time : 2022/10/7 下午1:10 6 | """ 7 | import template 8 | 9 | 10 | class Main(template.Template): 11 | def __init__(self): 12 | super().__init__() 13 | self.title = "哔哩哔哩视频(BILIBILI)" 14 | 15 | def query(self): 16 | p = self.params 17 | aid = "" 18 | page = 1 19 | vid="" 20 | if p["parse"].startswith("av"): 21 | p["parse"] = "https://www.bilibili.com/video/%s" % p["parse"] 22 | if p["parse"].startswith("BV"): 23 | p["parse"] = "https://www.bilibili.com/video/%s" % p["parse"] 24 | elif p["parse"].startswith("ep"): 25 | p["parse"] = "https://www.bilibili.com/bangumi/play/%s" % p["parse"] 26 | if p["parse"].startswith("http"): 27 | url = self.curl({"url": p["parse"], "response": "location"}) 28 | if "bangumi" in url: 29 | 30 | html = self.curl( 31 | { 32 | "url": url, 33 | "useragent": "ios", 34 | "encode": "gzip", 35 | "encoding": "utf8", 36 | } 37 | ) 38 | vid = self.match( 39 | [ 40 | # "(\d+)-\d+-\d+.m4s", 41 | "\d+\/(\d+)-.+?\.(?:mp4|flv)", 42 | # '"cid":(\d+)', 43 | "cid=(\d+)", 44 | 'cid="(\d+)', 45 | ], 46 | html, 47 | ) 48 | if vid == "0": 49 | vid = "" 50 | if not vid: 51 | loadded = self.match("window.__INITIAL_STATE__=(\{.*?\});", html) 52 | if loadded: 53 | loadJson = self.loads(loadded) 54 | vid = loadJson["epInfo"]["cid"] 55 | if vid == -1: 56 | vid = loadJson["epList"][0]["cid"] 57 | 58 | title = self.match( 59 | [ 60 | '"share_copy"\s*:\s*"([^"]+)"', 61 | '

', 62 | '"h1Title":"(.*?)"', 63 | '"title":"(.*?)"', 64 | ], 65 | html, 66 | ).replace("\u002F", "/") 67 | image = self.match('property="og:image" content="([^"]+)"', html) 68 | if image: 69 | image = image.replace("\u002F", "/") 70 | elif self.match("\/BV", url): 71 | bvid = self.match("\/(BV\w+)", url) 72 | bvUrl = f"http://api.bilibili.com/x/web-interface/view?bvid={bvid}" 73 | page = self.match(["\?p=(\d+)", "_(\d+).h"], url) or 1 74 | page = int(page) 75 | getBvSource = self.curl(bvUrl) 76 | self.logging.debug(f"getBvSource: {getBvSource} \r\n") 77 | bvJson = self.loads(getBvSource) 78 | if bvJson["code"] == 0: 79 | aid = bvJson["data"]["aid"] 80 | image = bvJson["data"]["pic"] 81 | title = bvJson["data"]["title"] 82 | cidData = bvJson["data"]["pages"][page - 1] 83 | vid = cidData["cid"] 84 | if "part" in cidData and page > 1: 85 | title = "第%s话_%s" % (title, cidData["part"]) 86 | else: 87 | aid = self.dec(bvid) 88 | 89 | elif "video" in url: 90 | aid = self.match("\/video\/av(\d+)", url) 91 | page = self.match(["_(\d+).h", "\?p=(\d+)"], url) or 1 92 | 93 | else: 94 | vid = p["parse"] 95 | 96 | if aid and not vid: 97 | page = int(page) 98 | viewUrl = "http://api.bilibili.com/view?appkey=12737ff7776f1ade&batch=1&page={}&id={}".format( 99 | page, aid 100 | ) 101 | 102 | getViewSource = self.curl(viewUrl) 103 | 104 | self.logging.debug(f"getInfo: {getViewSource} \r\n") 105 | if "Document is not exists" in getViewSource: 106 | raise NotImplementedError("hide") 107 | elif "Access denied" in getViewSource: 108 | raise NotImplementedError("cookie") 109 | else: 110 | data = self.loads(getViewSource) 111 | vid = data["cid"] 112 | image = data["pic"] 113 | title = data["title"] 114 | 115 | if "partname" in data and page > 1: 116 | title = "{}--{}".format(title, data["partname"]) 117 | return self.compact() 118 | 119 | def parse(self): 120 | timestamp = self.timestamp 121 | p = self.params 122 | assert p["vid"], "vid" 123 | vid = p["vid"] 124 | token = "" 125 | if self.cookie: 126 | getToken = self.curl( 127 | f"https://api.bilibili.com/x/player/playurl/token?cid={vid}&aid=91275169" 128 | ) 129 | tokenJson = self.loads(getToken) 130 | token = self.haskey(tokenJson, "data.token") 131 | 132 | # sk = {"appkey": "YvirImLGlLANCLvM", "appsecret": "JNlZNgfNGKZEpaDTkCdPQVXntXhuiJEM"} 133 | # sk = {"appkey": "bb3101000e232e27", "appsecret": "36efcfed79309338ced0380abd824ac1"} 134 | # sk = {"appkey": "07da50c9a0bf829f", "appsecret": "25bdede4e1581c836cab73a48790ca6e"} 135 | # sk = {"appkey": "4409e2ce8ffd12b8", "appsecret": "59b43e04ad6965f34319062b478f83dd"} 136 | sk = { 137 | "appkey": "iVGUTjsxvpLeuDCf", 138 | "appsecret": "aHRmhWMLkdeMuILqORnYZocwMBpMEOdt", 139 | } 140 | 141 | otype = "video" 142 | qn = 80 143 | 144 | mParams = [ 145 | ("appkey", sk["appkey"]), 146 | ("build", "4140"), 147 | ("buvid", "ef5efdc0452641eb235cd424dfa03660"), 148 | ("cid", vid), 149 | ("device", "phone"), 150 | ("otype", "json"), 151 | ("platform", "html5"), 152 | ("qn", qn), 153 | ("type", "mp4"), 154 | ] 155 | mEncrypt = self.urlencode(mParams) 156 | mUrl = "https://app.bilibili.com/v2/playurl?%s&sign=%s" % ( 157 | mEncrypt, 158 | self.md5(mEncrypt + sk["appsecret"]), 159 | ) 160 | 161 | mSource = self.curl(mUrl) 162 | 163 | mJson = self.loads(mSource) 164 | 165 | if "message" in mJson: 166 | if mJson["message"] not in ["Video is hidden."]: 167 | raise NotImplementedError(mJson["message"]) 168 | else: 169 | otype = "bangumi" 170 | 171 | mParams = [ 172 | ("cid", vid), 173 | ("module", "bangumi"), 174 | ("otype", "json"), 175 | ("platform", "html5"), 176 | ("player", "1"), 177 | ("qn", qn), 178 | ("ts", timestamp), 179 | ("type", "mp4"), 180 | ] 181 | mEncrypt = self.urlencode(mParams) 182 | mSign = self.md5(mEncrypt + "9b288147e5474dd2aa67085f716c560d") 183 | 184 | mUrl = ( 185 | "https://bangumi.bilibili.com/player/web_api/playurl?%s&sign=%s" 186 | % ( 187 | mEncrypt, 188 | mSign, 189 | ) 190 | ) 191 | mSource = self.curl(mUrl) 192 | mJson = self.loads(mSource) 193 | 194 | self.logging.debug(f"mSource: {mSource} \r\n") 195 | 196 | assert "8986943" not in mSource, "area" 197 | assert "durl" in mJson, "lists" 198 | 199 | mp4 = mJson["durl"][0]["url"] 200 | 201 | flvQn = 80 202 | for i in range(2): 203 | if otype == "video": 204 | flvParams = [ 205 | ("appkey", sk["appkey"]), 206 | ("build", "4140"), 207 | ("buvid", "ef5efdc0452641eb235cd424dfa03660"), 208 | ("cid", vid), 209 | ("device", "phone"), 210 | ("otype", "json"), 211 | ("platform", "html5"), 212 | ("qn", flvQn), 213 | ("quality", flvQn), 214 | ("type", "flv"), 215 | ] 216 | 217 | flvEncrypt = self.urlencode(flvParams) 218 | 219 | flvUrl = "https://interface.bilibili.com/v2/playurl?%s&sign=%s" % ( 220 | flvEncrypt, 221 | self.md5(flvEncrypt + sk["appsecret"]), 222 | ) 223 | if token: 224 | flvUrl += f"&utoken={token}" 225 | else: 226 | flvParams = [ 227 | ("cid", vid), 228 | ("module", "bangumi"), 229 | ("otype", "json"), 230 | ("platform", "html5"), 231 | ("player", "1"), 232 | ("qn", flvQn), 233 | ("ts", timestamp), 234 | ("type", "flv"), 235 | ] 236 | flvEncrypt = self.urlencode(flvParams) 237 | flvSign = self.md5(flvEncrypt + "9b288147e5474dd2aa67085f716c560d") 238 | 239 | flvUrl = ( 240 | "https://bangumi.bilibili.com/player/web_api/playurl?%s&sign=%s" 241 | % (flvEncrypt, flvSign) 242 | ) 243 | 244 | getFlvSource = self.curl(flvUrl) 245 | getFlvJson = self.loads(getFlvSource) 246 | qns = sorted(getFlvJson["accept_quality"]) 247 | 248 | flvQn = self.data(qns, p["hd"]) 249 | if flvQn >= qn and not self.cookie: 250 | break 251 | self.logging.debug(f"getFlvSource: {getFlvSource} \r\n") 252 | 253 | duration = getFlvJson["timelength"] / 1000 254 | if "url" in getFlvJson["durl"]: 255 | segs = [ 256 | { 257 | "url": getFlvJson["durl"]["url"], 258 | "duration": duration, 259 | "size": int(getFlvJson["durl"]["size"]), 260 | } 261 | ] 262 | else: 263 | segs = [ 264 | { 265 | "url": i["url"], 266 | "duration": i["length"] / 1000, 267 | "size": int(i["size"]), 268 | } 269 | for i in getFlvJson["durl"] 270 | ] 271 | ext = "flv" if segs[0]["url"].find(".flv") > 0 else "mp4" 272 | 273 | quality = getFlvJson["accept_description"][::-1] 274 | dicts = dict( 275 | zip(getFlvJson["accept_quality"], getFlvJson["accept_description"]) 276 | ) 277 | quality = [dicts[i] for i in qns] 278 | # 1800p+分辨率要会员cookie 279 | regx = f"-1-{flvQn}" 280 | if p["hd"] > 4 and regx not in segs[0]["url"] and not self.cookie: 281 | quality = quality[:4] 282 | show = self.data(quality, p["hd"]) 283 | 284 | extra = { 285 | "headers": { 286 | "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:71.0) Gecko/20100101 Firefox/71.0", 287 | "Referer": "https://www.bilibili.com/video/", 288 | } 289 | } 290 | if len(segs) == 1 and ext == "flv": 291 | flv = segs[0]["url"] 292 | extra["playback"] = "flv" 293 | return self.compact() 294 | 295 | def dec(x): 296 | # https://www.zhihu.com/question/381784377/answer/1099438784 297 | tr = { 298 | "f": 0, 299 | "Z": 1, 300 | "o": 2, 301 | "d": 3, 302 | "R": 4, 303 | "9": 5, 304 | "X": 6, 305 | "Q": 7, 306 | "D": 8, 307 | "S": 9, 308 | "U": 10, 309 | "m": 11, 310 | "2": 12, 311 | "1": 13, 312 | "y": 14, 313 | "C": 15, 314 | "k": 16, 315 | "r": 17, 316 | "6": 18, 317 | "z": 19, 318 | "B": 20, 319 | "q": 21, 320 | "i": 22, 321 | "v": 23, 322 | "e": 24, 323 | "Y": 25, 324 | "a": 26, 325 | "h": 27, 326 | "8": 28, 327 | "b": 29, 328 | "t": 30, 329 | "4": 31, 330 | "x": 32, 331 | "s": 33, 332 | "W": 34, 333 | "p": 35, 334 | "H": 36, 335 | "n": 37, 336 | "J": 38, 337 | "E": 39, 338 | "7": 40, 339 | "j": 41, 340 | "L": 42, 341 | "5": 43, 342 | "V": 44, 343 | "G": 45, 344 | "3": 46, 345 | "g": 47, 346 | "u": 48, 347 | "M": 49, 348 | "T": 50, 349 | "K": 51, 350 | "N": 52, 351 | "P": 53, 352 | "A": 54, 353 | "w": 55, 354 | "c": 56, 355 | "F": 57, 356 | } 357 | s = [11, 10, 3, 8, 4, 6] 358 | xor = 177451812 359 | add = 8728348608 360 | r = 0 361 | for i in range(6): 362 | r += tr[x[s[i]]] * 58**i 363 | return (r - add) ^ xor 364 | 365 | def enc(x): 366 | table = "fZodR9XQDSUm21yCkr6zBqiveYah8bt4xsWpHnJE7jL5VG3guMTKNPAwcF" 367 | s = [11, 10, 3, 8, 4, 6] 368 | xor = 177451812 369 | add = 8728348608 370 | x = (int(x) ^ xor) + add 371 | r = list("BV1 4 1 7 ") 372 | for i in range(6): 373 | r[s[i]] = table[x // 58**i % 58] 374 | return "".join(r) 375 | -------------------------------------------------------------------------------- /qito/parse/video/cntv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : cntv.py 5 | @Time : 2022/11/14 下午4:00 6 | """ 7 | import template 8 | 9 | 10 | class Main(template.Template): 11 | def __init__(self): 12 | super().__init__() 13 | self.title = "央视视频(CNTV)" 14 | 15 | def query(self): 16 | p = self.params 17 | if p["parse"].startswith("http"): 18 | html = self.curl(p["parse"]) 19 | vid = self.match( 20 | [ 21 | '"videoCenterId","(\w+)"', 22 | 'itemguid="(\w+)"', 23 | 'guid\s*=\s*"(\w+)"', 24 | "initMyAray\s*=\s*'([^']+)'", 25 | ], 26 | html, 27 | ) 28 | else: 29 | vid = p["parse"] 30 | assert vid, "vid" 31 | if p.get("query"): 32 | info = self.curl(f"http://vdn.apps.cntv.cn/api/getHttpVideoInfo.do?pid={vid}") 33 | self.logging.debug(f"getVideoInfo: {info} \r\n") 34 | json = self.loads(info) 35 | if json["ack"] == "yes": 36 | title = json["title"] 37 | duration = json["video"]["totalLength"] 38 | image = json["video"]["chapters"][0]["image"] 39 | return self.compact() 40 | 41 | def parse(self): 42 | p = self.params 43 | vid = p["vid"] 44 | 45 | info = self.curl( 46 | f"http://vdn.apps.cntv.cn/api/getHttpVideoInfo.do?pid={vid}&vn=2054&vc=&pcv=152438790&uid=&wlan=" 47 | ) 48 | self.logging.debug(f"getHttpVideoInfo: {info} \r\n") 49 | json = self.loads(info) 50 | assert "video" in json, "data" 51 | title = json["title"] 52 | duration = json["video"]["totalLength"] 53 | image = json["video"]["chapters"][0]["image"] 54 | 55 | ary = ["chapters", "chapters2", "chapters3", "chapters4", "chapters5", "chapters6"] 56 | qua = ["流畅", "标清", "高清", "超清"] 57 | videos = [json["video"][i] for i in ary if i in json["video"]] 58 | quality = qua[: len(videos)] 59 | show = self.data(quality, p["hd"]) 60 | data = self.data(videos, p["hd"]) 61 | m3u8 = json["hls_url"] 62 | playback = "m3u8" 63 | segs = [{"url": i["url"], "duration": i["duration"]} for i in data] 64 | 65 | return self.compact() 66 | -------------------------------------------------------------------------------- /qito/parse/video/douyin.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : douyin.py 5 | @Time : 2022/11/12 下午1:23 6 | """ 7 | import template 8 | 9 | 10 | class Main(template.Template): 11 | def __init__(self): 12 | super().__init__() 13 | self.title = "抖音视频(DOUYIN)" 14 | 15 | def query(self): 16 | p = self.params 17 | if not self.hasurl(p["parse"]) and not p["parse"].isdigit(): 18 | p["parse"] = f"https://v.douyin.com/{p['parse']}/" 19 | 20 | if self.hasurl(p["parse"]): 21 | url = self.curl({"url": p["parse"], "response": "location"}) 22 | 23 | aid = self.match(["\/video\/(\d+)", "mid=(\d+)"], url) 24 | api = f"https://www.iesdouyin.com/web/api/v2/aweme/iteminfo/?item_ids={aid}" 25 | 26 | html = self.curl(api) 27 | 28 | json = self.loads(html) 29 | 30 | video = self.haskey(json, "item_list.0.video", "", "data") 31 | 32 | vid = video["vid"] 33 | image = video["origin_cover"]["url_list"][0] 34 | title = json["item_list"][0]["desc"] 35 | return self.compact() 36 | 37 | def parse(self): 38 | p = self.params 39 | assert p["vid"], "vid" 40 | url = f"http://i.snssdk.com/video/urls/1/toutiao/mp4/{p['vid']}?watermark=0&h265=0&nobase64=1" 41 | html = self.curl(url) 42 | json = self.loads(html) 43 | 44 | data = self.haskey(json, "data", "", "data") 45 | videoList = data["video_list"] 46 | lists = self.column(videoList, "", "definition") 47 | assert lists, "lists" 48 | image = data["poster_url"] 49 | duration = data["video_duration"] 50 | quality = list(lists.keys()) 51 | show = self.data(quality, p["hd"]) 52 | mp4 = lists[show]["main_url"] 53 | size = lists[show]["size"] 54 | return self.compact() 55 | -------------------------------------------------------------------------------- /qito/parse/video/douyu.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : douyu.py 5 | @Time : 2022/11/30 上午10:32 6 | """ 7 | import template 8 | 9 | 10 | class Main(template.Template): 11 | def __init__(self): 12 | super().__init__() 13 | self.title = "斗鱼视频(DOUYU)" 14 | self.require = [("quickjs", "Function", "fn"), "execjs", "re"] 15 | 16 | def query(self): 17 | p = self.params 18 | if self.hasurl(p["parse"]): 19 | aid = self.match("\/show\/(\w+)", p["parse"]) 20 | else: 21 | aid = p["parse"] 22 | html = self.curl(f"https://v.douyu.com/show/{aid}") 23 | data = self.match("\$DATA\s*=\s*([^<]+?);\s*\<", html) 24 | 25 | self.logging.debug(f"roomData: {data} \r\n") 26 | # json = self.jsonParse(self.quoteJson(data)) 27 | 28 | json = self.jsonParse(data) 29 | if "ROOM" in json: 30 | title = json["ROOM"]["name"] 31 | image = json["ROOM"]["pic"] 32 | duration = json["ROOM"]["duration"] 33 | vid = json["ROOM"]["point_id"] 34 | 35 | return self.compact() 36 | 37 | def parse(self): 38 | p = self.params 39 | assert p["vid"], "vid" 40 | vid = p["vid"] 41 | aid = p["aid"] 42 | timestamp = self.time 43 | crypto = self.read(f"{self.abspath}/tool/javascript/crypto-js.min.js") 44 | url = f"https://www.douyu.com/swf_api/homeH5Enc?rids={vid}" 45 | getEnc = self.curl(url) 46 | json = self.loads(getEnc) 47 | assert json.get("error") == 0, "homeH5enc" 48 | jsEnc = json["data"][f"room{vid}"] 49 | evalString = """ 50 | //eval(Ee); 51 | var newString = "'" + Ee + "';"; 52 | var evalString = eval(newString); 53 | evalString = evalString.replace(/(.*oog\(.*)/, "$1oog['test'] = function(a) { return 1; }"); 54 | eval(evalString); 55 | """ 56 | jsDom = jsEnc.replace("eval(Ee);", evalString) 57 | dom = "let window = {},document = {};" 58 | try: 59 | f = self.modules["fn"]("ub98484234", f"{crypto};{dom};{jsDom};") 60 | ub98484234 = f(vid, self.md5(timestamp), timestamp) 61 | except: 62 | ctx = self.modules["execjs"].compile(f"{crypto};{dom};{jsDom};") 63 | ub98484234 = ctx.call("ub98484234", vid, self.md5(timestamp), timestamp) 64 | 65 | self.logging.debug(f"ub98484234: {ub98484234} \r\n") 66 | postData = self.qsl(f"{ub98484234}&vid={aid}") 67 | 68 | getStream = self.curl( 69 | "https://v.douyu.com/api/stream/getStreamUrl", 70 | {"method": "post", "form": postData}, 71 | ) 72 | self.logging.debug(f"getStream: {getStream} \r\n") 73 | data = self.loads(getStream) 74 | 75 | assert data.get("error") == 0, "data" 76 | dicts = {"normal": "高清", "high": "超清", "super": "原画"} 77 | video = data["data"]["thumb_video"] 78 | s = self.sort([k for k, v in video.items() if v], dicts.keys()) 79 | quality = [dicts[i] for i in s] 80 | show = self.data(quality, p["hd"]) 81 | m3u8 = video[self.data(s, p["hd"])]["url"] 82 | playback = ext = "m3u8" 83 | return self.compact() 84 | 85 | def quoteJson(self, json_str): 86 | re = self.modules["re"] 87 | quote_pat = re.compile(r'"[^"]*"') 88 | a = quote_pat.findall(json_str) 89 | json_str = quote_pat.sub("@", json_str) 90 | key_pat = re.compile(r"(\w+):") 91 | json_str = key_pat.sub(r'"\1":', json_str) 92 | assert json_str.count("@") == len(a) 93 | count = -1 94 | 95 | def put_back_values(match): 96 | nonlocal count 97 | count += 1 98 | return a[count] 99 | 100 | json_str = re.sub("@", put_back_values, json_str) 101 | return json_str 102 | -------------------------------------------------------------------------------- /qito/parse/video/huya.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : huya.py 5 | @Time : 2022/11/30 上午12:07 6 | """ 7 | import template 8 | 9 | 10 | class Main(template.Template): 11 | def __init__(self): 12 | super().__init__() 13 | self.title = "虎牙视频(HUYA)" 14 | 15 | def query(self): 16 | p = self.params 17 | if p["parse"].startswith("http"): 18 | vid = self.match("\/(\d+)", p["parse"]) 19 | else: 20 | vid = p["parse"] 21 | if p.get("query"): 22 | url = f"https://liveapi.huya.com/moment/getMomentContent?cal&videoId={vid}" 23 | html = self.curl(url) 24 | self.logging.debug(f"getMomentContent: {html} \r\n") 25 | json = self.loads(html) 26 | if json["status"] == 200: 27 | data = json["data"] 28 | title = data["moment"]["videoInfo"]["videoTitle"] 29 | image = data["moment"]["videoInfo"]["videoCover"] 30 | duration = 0 31 | for d in data["moment"]["videoInfo"]["videoDuration"].split(":"): 32 | duration = duration * 60 + int(d) 33 | 34 | return self.compact() 35 | 36 | def parse(self): 37 | p = self.params 38 | assert p["vid"], "vid" 39 | vid = p["vid"] 40 | url = f"https://liveapi.huya.com/moment/getMomentContent?cal&videoId={vid}" 41 | html = self.curl(url) 42 | self.logging.debug(f"getVideo: {html} \r\n") 43 | json = self.loads(html) 44 | assert json["status"] == 200, "data" 45 | data = json["data"] 46 | title = data["moment"]["videoInfo"]["videoTitle"] 47 | image = data["moment"]["videoInfo"]["videoCover"] 48 | 49 | duration = 0 50 | for d in data["moment"]["videoInfo"]["videoDuration"].split(":"): 51 | duration = duration * 60 + int(d) 52 | 53 | lists = self.column(data["moment"]["videoInfo"]["definitions"], "", "defName") 54 | 55 | quality = list(lists.keys())[::-1] 56 | show = self.data(quality, p["hd"]) 57 | video = lists[show] 58 | 59 | mp4 = video["url"] 60 | m3u8 = video["m3u8"] 61 | size = int(video["size"]) 62 | segs = [{"url": mp4, "duration": duration, "size": size}] 63 | return self.compact() 64 | -------------------------------------------------------------------------------- /qito/parse/video/iqiyi.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : iqiyi.py 5 | @Time : 2022/9/26 下午7:26 6 | """ 7 | import template 8 | 9 | 10 | class Main(template.Template): 11 | def __init__(self): 12 | super().__init__() 13 | self.title = "爱奇艺视频(IQIYI)" 14 | 15 | def query(self): 16 | p = self.params 17 | if self.hasurl(p["parse"]): 18 | html = self.curl(p["parse"]) 19 | vid = self.match( 20 | [ 21 | 'data-player-videoid="(\w+)"', 22 | '"vid":"(\w+)"', 23 | "param['vid'] = \"(\w+)\"", 24 | ], 25 | html, 26 | ) 27 | cid = self.match( 28 | [ 29 | "tvId:(\d+)", 30 | 'data-player-tvid="(\d+)"', 31 | "param['tvid'] = \"(\d+)\"", 32 | '"tvid":"(\d+)"', 33 | '"tvId":(\d+)', 34 | ], 35 | html, 36 | ) 37 | pay = 1 if self.match('"payMark":(\d+)', html) else 0 38 | else: 39 | string = self.replace(["|", ";", ",", "-", "_", ":"], "-", p["parse"]) 40 | spl = string.split("-") 41 | if len(spl) == 2: 42 | vid = spl[0] if len(spl[0]) == 32 else spl[1] 43 | cid = spl[1] if len(spl[0]) == 32 else spl[0] 44 | elif len(spl[0]) == 32: 45 | cid = "" 46 | else: 47 | cid = spl[0] 48 | assert cid, "tvid" 49 | getBaseInfo = self.curl( 50 | { 51 | "url": f"https://mac.video.iqiyi.com/video/video/baseinfo/{cid}?src=01082001010000000000", 52 | "decoding": "utf8", 53 | }, 54 | ) 55 | self.logging.debug(f"baseInfo: {getBaseInfo} \r\n") 56 | baseJson = self.loads(getBaseInfo) 57 | 58 | if baseJson.get("data"): 59 | vid = baseJson["data"]["vid"] 60 | image = baseJson["data"]["imageUrl"].replace(".jpg", "_480_270.jpg") 61 | title = baseJson["data"]["name"] 62 | if "subtitle" in baseJson["data"]: 63 | title += " " + baseJson["data"]["subtitle"] 64 | pay = "1" if baseJson["data"]["payMark"] else "" 65 | duration = self.seconds(baseJson["data"]["duration"]) 66 | else: 67 | getVideoInfo = self.curl( 68 | f"https://pcw-api.iqiyi.com/video/video/playervideoinfo?tvid={cid}" 69 | ) 70 | self.logging.debug(f"videoInfo: {getVideoInfo} \r\n") 71 | data = self.loads(getVideoInfo) 72 | assert data.get("data"), "data" 73 | if not vid: 74 | source = data["data"]["vu"] 75 | html = self.curl(source) 76 | vid = self.match( 77 | [ 78 | 'data-player-videoid="(\w+)"', 79 | '"vid":"(\w+)"', 80 | "param['vid'] = \"(\w+)\"", 81 | ], 82 | html, 83 | ) 84 | pay = 1 if self.match('"payMark":(\d+)', html) else 0 85 | image = data["data"]["vpic"] 86 | title = data["data"]["vn"] 87 | if "subt" in data["data"]: 88 | title += " " + data["data"]["subt"] 89 | 90 | tm = self.timestamp * 1000 91 | rand = self.md5(tm) 92 | key = "d5fb4bd9d50c4be6948c97edd7254b0e" 93 | src = "76f90cbd92f94a2e925d83e8ccd22cb7" 94 | sc = self.md5(f"{tm}{key}{vid}") 95 | url = f"https://cache.m.iqiyi.com/tmts/{cid}/{vid}/?sc={sc}&src={src}&t={tm}" 96 | tmts = self.curl(url) 97 | tmtsJson = self.loads(tmts) 98 | 99 | if self.haskey(tmtsJson, "data.ctl.configs"): 100 | configs = tmtsJson["data"]["ctl"]["configs"] 101 | vids = configs.get("19") or configs.get("10") 102 | if vids: 103 | vid = vids["vid"] 104 | return self.compact() 105 | 106 | def parse(self): 107 | 108 | p = self.params 109 | assert p["vid"], "vid" 110 | vid = p["vid"] 111 | tvid = p["cid"] 112 | timestamp = self.timestamp 113 | t = timestamp * 1000 114 | try: 115 | if self.haskey(p, "context.tmts"): 116 | tmts = p["context"]["tmts"] 117 | else: 118 | tm = self.timestamp * 1000 119 | rand = self.md5(tm) 120 | key = "d5fb4bd9d50c4be6948c97edd7254b0e" 121 | src = "76f90cbd92f94a2e925d83e8ccd22cb7" 122 | sc = self.md5(f"{tm}{key}{vid}") 123 | url = f"https://cache.m.iqiyi.com/tmts/{tvid}/{vid}/?sc={sc}&src={src}&t={tm}" 124 | tmts = self.curl(url) 125 | 126 | self.logging.debug(f"getTmts: {tmts} \r\n") 127 | json = self.jsonParse(tmts) 128 | assert json.get("code") == "A00000", "data" 129 | dicts = {"h265": {}, "h264": {}} 130 | for i in json["data"]["vidl"]: 131 | types = "h265" if i.get("fileFormat") == "H265" else "h264" 132 | dicts[types][i["vd"]] = i 133 | if p.get("encoder") == "h265": 134 | lists = dicts["h265"] 135 | else: 136 | 137 | lists = dicts["h264"] 138 | 139 | lists[json["data"]["vd"]] = { 140 | "screenSize": json["data"]["screenSize"], 141 | "m3u": json["data"]["m3u"], 142 | } 143 | vs = {int(v["screenSize"].split("x")[0]): k for k, v in lists.items()} 144 | 145 | vsize = sorted(vs.keys()) 146 | quality = [str(vs[i]) for i in vsize] 147 | show = self.data(quality, p["hd"]) 148 | data = lists[int(show)] 149 | m3u8 = data["m3u"] 150 | playback = ext = "m3u8" 151 | except: 152 | src = f"/vps?tvid={tvid}&vid={vid}&v=0&src=01012001010000000000&t={t}&k_tag=1&k_uid={self.md5(t)}&rs=1" 153 | 154 | vf = self.md5x(src) 155 | url = f"http://cache.video.qiyi.com/vps?tvid=385274600&vid=385274600&v=0&qypid=385274600_12&src=01012001010000000000&t=1668247341473&k_tag=1&k_uid=uxjgywyzvub06btmsonm9olkno2otqm7&rs=1&vf=1aef6bada250012cdf8967cb9f35e932" 156 | html = self.curl(url) 157 | self.logging.debug(f"getVps: {html} \r\n") 158 | json = self.loads(html) 159 | assert json["code"] == "A00000", "data" 160 | # for i in (json['data']['vp']['tkl'][0]['vs']): 161 | # print(i) 162 | column = self.column(json["data"]["vp"]["tkl"][0]["vs"], "", "bid") 163 | vsize = self.column(json["data"]["vp"]["tkl"][0]["vs"], "bid", "vsize") 164 | 165 | quality = [str(vsize[k]) for k in sorted(vsize)] 166 | 167 | show = self.data(quality, p["hd"]) 168 | data = column[int(show)] 169 | size = data["vsize"] 170 | du = json["data"]["vp"]["du"] 171 | segs = [{"url": f"{du}{i['l']}&pv=0.2"} for i in data["fs"]] 172 | 173 | ext = "f4v" 174 | try: 175 | bids = [100, 200, 300, 500, 600] 176 | iqiyiBid = self.data(bids, p["hd"]) 177 | iqiyiBid = 620 178 | 179 | dashParams = { 180 | "tvid": tvid, 181 | "bid": iqiyiBid, 182 | "vid": vid, 183 | "src": "01080031010000000000", 184 | "vt": "0", 185 | "rs": "1", 186 | "uid": "", 187 | # "ori": "pcw", 188 | "ps": "1", 189 | "k_uid": "c55d485ee178762fe5e2135b9bddf52d", 190 | "pt": "0", 191 | "d": "0", 192 | "s": "", 193 | "lid": "", 194 | "cf": "", 195 | "ct": "", 196 | "authKey": "", 197 | "k_tag": "-1", 198 | "ost": "undefined", 199 | "ppt": "undefined", 200 | "dfp": "", 201 | "locale": "zh_cn", 202 | "prio": '{"ff":"m3u8","code":2}', 203 | "pck": "", 204 | "k_err_retries": "0", 205 | "up": "", 206 | "qd_v": "2", 207 | "tm": self.timestamp, 208 | "qdy": "i", 209 | "qds": "0", 210 | "k_ft1": "755914244096", 211 | # "k_ft4": "-1", 212 | "k_ft5": "1", 213 | "bop": '{"version":"10.0","dfp":""}', 214 | "ut": "1", 215 | } 216 | dash = f"/dash?{self.urlencode(dashParams)}" 217 | 218 | vf = self.cmd5x(dash) 219 | 220 | url = f"http://cache.video.iqiyi.com{dash}&vf={vf}" 221 | 222 | html = self.curl(url) 223 | 224 | self.logging.debug(f"getVideo: {html} \r\n") 225 | data = self.loads(html) 226 | fmtDict = {"f4v": [], "ts": [], "265ts": [], "dash": []} 227 | for i in data["data"]["program"]["video"]: 228 | if i["bid"] not in fmtDict[i["ff"]]: 229 | fmtDict[i["ff"]].append(i["bid"]) 230 | 231 | if fmtDict["265ts"]: 232 | ff = fmtDict["265ts"] 233 | fmt = "h265" 234 | lists = { 235 | "极速[H265]": "100", 236 | "流畅[H265]": "200", 237 | "高清[H265]": "300", 238 | "720P[H265]": "500", 239 | "1080P[H265]": "600", 240 | "4K[H265]": "800", 241 | } 242 | elif fmtDict["f4v"]: 243 | ff = fmtDict["f4v"] 244 | fmt = "f4v" 245 | else: 246 | ff = fmtDict["ts"] 247 | fmt = "h264" 248 | 249 | ff.sort() 250 | if p.get("encoder") == "h265": 251 | lists = { 252 | 100: "H265_极速", 253 | 200: "H265_流畅", 254 | 300: "H265_高清", 255 | 500: "H265_720P", 256 | 600: "H265_1080P", 257 | 620: "H265_1080P50", 258 | 800: "H265_4k", 259 | } 260 | 261 | elif p.get("encoder") == "dolby": 262 | lists = { 263 | 100: "杜比_极速", 264 | 200: "杜比_流畅", 265 | 300: "杜比_高清", 266 | 500: "杜比_720P", 267 | 600: "杜比_1080P", 268 | 610: "杜比_1080P50", 269 | } 270 | else: 271 | lists = { 272 | 100: "极速", 273 | 200: "流畅", 274 | 300: "高清", 275 | 500: "720P", 276 | 600: "1080P", 277 | 610: "1080P50", 278 | } 279 | 280 | assert "msg" not in data, "rule" 281 | assert "program" in data["data"], "data" 282 | quality = [lists[i] for i in ff] 283 | show = self.data(quality, p["hd"]) 284 | info = tuple( 285 | [i["url"], i["duration"], i["vsize"]] 286 | for i in data["data"]["program"]["video"] 287 | if "url" in i and "http" in i["url"] 288 | ) 289 | m3u8, duration, size123 = info[0] 290 | 291 | playback = "m3u8" 292 | 293 | # ext = "hls" 294 | # extra = {"replace": ["http:", "https:"]} 295 | extra = { 296 | "headers": { 297 | "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.16; rv:84.0) Gecko/20100101 Firefox/84.0", 298 | "Accept": "*/*", 299 | "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2", 300 | "Referer": "https://www.iqiyi.com/v_19rr5aq6z8.html", 301 | "Origin": "https://www.iqiyi.com", 302 | "Connection": "keep-alive", 303 | "Pragma": "no-cache", 304 | "Cache-Control": "no-cache", 305 | } 306 | } 307 | except: 308 | pass 309 | 310 | # 获取m3u8 311 | 312 | return self.compact() 313 | 314 | 315 | def md5x(self, dash): 316 | return self.md5(dash + "1j2k2k3l3l4m4m5n5n6o6o7p7p8q8q9r") 317 | 318 | def cmd5x(self, dash): 319 | 320 | js = self.read(self.abspath + "/tool/javascript/iqiyi.cmd5x.js") 321 | 322 | try: 323 | from quickjs import Function 324 | 325 | f = Function("cmd5x", js) 326 | vf = f(dash) 327 | 328 | except: 329 | import execjs 330 | 331 | ctx = execjs.compile(js) 332 | vf = ctx.call("cmd5x", dash) 333 | return vf 334 | 335 | def temp(self, vid): 336 | path = f"{self.abspath}/temp/{vid}.m3u8" 337 | return path 338 | -------------------------------------------------------------------------------- /qito/parse/video/ixigua.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : ixigua.py 5 | @Time : 2022/10/14 上午8:44 6 | """ 7 | import template 8 | 9 | 10 | class Main(template.Template): 11 | def __init__(self): 12 | super().__init__() 13 | self.title = "西瓜视频(IXIGUA)" 14 | 15 | def query(self): 16 | p = self.params 17 | if p["parse"].isdigit(): 18 | p["parse"] = f"https://www.ixigua.com/i{p['parse']}/" 19 | if self.hasurl(p["parse"]): 20 | 21 | p["parse"] += "&wid_try=1" if "?" in p["parse"] else "?wid_try=1" 22 | html = self.curl( 23 | { 24 | "url": p["parse"], 25 | "encoding": "utf-8", 26 | "cookie": "__ac_nonce=05348ace2003276552d00", 27 | } 28 | ) 29 | context = {"html": html} 30 | 31 | vid = self.match(['"video_id"\s*:\s*"(\w+)"'], html) 32 | title = self.match( 33 | 'title\s*data-react-helmet="true"\>([^-]+)', html 34 | ).strip() 35 | else: 36 | vid = p["parse"] 37 | return self.compact() 38 | 39 | def parse(self): 40 | p = self.params 41 | assert p["vid"], "vid" 42 | vid = p["vid"] 43 | try: 44 | if self.haskey(p, "context.html"): 45 | html = p["context"]["html"] 46 | else: 47 | 48 | html = self.curl(p["parse"]) 49 | 50 | ssrData = self.match("_SSR_HYDRATED_DATA\s*=\s*([^\<]+)", html) 51 | 52 | self.logging.debug(f"getJson: {ssrData} \r\n") 53 | assert ssrData, "data" 54 | 55 | json = self.loads(ssrData.replace(":undefined", ':"undefined"')) 56 | 57 | packerData = self.haskey( 58 | json, "anyVideo.gidInformation.packerData.video" 59 | ) or self.haskey(json, "anyVideo.gidInformation.packerData") 60 | videoList = packerData["videoResource"]["normal"]["video_list"] 61 | 62 | lists = self.column(videoList.values(), "", "definition") 63 | 64 | quality = list(sorted(lists, key=lambda x: x[0])) 65 | 66 | show = self.data(quality, p["hd"]) 67 | 68 | mp4 = self.b64decode(lists[show]["main_url"]) 69 | 70 | size = lists[show]["size"] 71 | 72 | duration = packerData["videoResource"]["dash"]["video_duration"] 73 | title = packerData["episodeInfo"]["title"] 74 | image = packerData["episodeInfo"]["coverList"][0]["url"] 75 | 76 | except: 77 | url = f"http://i.snssdk.com/video/urls/1/toutiao/mp4/{vid}?watermark=0&h265=0&nobase64=1" 78 | html = self.curl(url) 79 | self.logging.debug(f"getStaticVideo: {html} \r\n") 80 | json = self.loads(html) 81 | assert "data" in json, "rule" 82 | videoList = json["data"]["video_list"] 83 | lists = self.column(videoList.values(), "", "definition") 84 | assert lists, "lists" 85 | quality = list(lists.keys()) 86 | show = self.data(quality, p["hd"]) 87 | mp4 = lists[show]["main_url"] 88 | segs = [{"url": mp4, "size": lists[show]["size"]}] 89 | image = json["data"]["poster_url"] 90 | duration = json["data"]["video_duration"] 91 | return self.compact() 92 | -------------------------------------------------------------------------------- /qito/parse/video/le.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : le.com.py 5 | @Time : 2022/2/13 下午6:13 6 | """ 7 | import template 8 | 9 | 10 | class Main(template.Template): 11 | def __init__(self): 12 | super().__init__() 13 | self.title = "乐视视频(LE)" 14 | 15 | def query(self): 16 | p = self.params 17 | if self.hasurl(p["parse"]): 18 | vid = self.match("\/(\d+)", p["parse"]) 19 | else: 20 | vid = p["parse"] 21 | if p.get("query"): 22 | key = self.getKey(self.time) 23 | playParams = { 24 | "platid": "1", 25 | "splatid": "105", 26 | "tss": "ios", 27 | "id": vid, 28 | "detect": "0", 29 | "dvtype": "1000", 30 | "accessyx": "1", 31 | "domain": "m.le.com", 32 | "tkey": key, 33 | "devid": "6E622B75C984100A4495B527DCD5FB56F585580C", 34 | "source": "1001", 35 | "lang": "en", 36 | "region": "cn", 37 | "isHttps": "0", 38 | } 39 | content = self.curl( 40 | { 41 | "url": "http://player-pc.le.com/mms/out/video/playJson.json", 42 | "params": playParams, 43 | } 44 | ) 45 | self.logging.debug(f"playJson: {content}") 46 | data = self.loads(content) 47 | if self.haskey(data, "msgs.playurl"): 48 | mid = data["msgs"]["playurl"]["mid"][1:-1] 49 | title = data["msgs"]["playurl"]["title"] 50 | image = sorted(data["msgs"]["playurl"]["picAll"].values())[-1] 51 | 52 | return self.compact() 53 | 54 | def parse(self): 55 | 56 | p = self.params 57 | assert p["vid"], "vid" 58 | pcode = "010210000" 59 | url = f"http://t.api.mob.app.letv.com/play?tm={self.time}&playid=0&tss=ios&pcode={pcode}&version=6.0&pid=93327&vid={p['vid']}&v=android&res=json&_debug=1" 60 | html = self.curl(url) 61 | self.logging.debug(f"getPlaySource: {html} \r\n") 62 | 63 | playJson = self.loads(html) 64 | assert self.haskey(playJson, "header.status", "1"), "data" 65 | ary = ["mp4", "180", "350", "1000", "800", "1300", "720p", "1080p", "1080p3m"] 66 | dispatch = playJson["body"]["videofile"]["infos"] 67 | lists = dict([[i.replace("mp4_", ""), dispatch[i]] for i in dispatch]) 68 | quality = list(lists.keys()) 69 | quality.sort(key=ary.index) 70 | show = self.data(quality, p["hd"]) 71 | size = lists[show]["filesize"] 72 | m3u8Url = lists[show]["mainUrl"] 73 | mp4 = self.curl( 74 | {"url": m3u8Url.replace("tss=ios", "tss=no"), "response": "json"} 75 | )["location"] 76 | title = playJson["body"]["videoInfo"]["nameCn"] 77 | duration = playJson["body"]["videoInfo"]["duration"] 78 | image = list(playJson["body"]["videoInfo"]["picAll"].values())[-1] 79 | extra = { 80 | "headers": { 81 | "User-Agent": "LetvIphoneClient/9.23.3 (iPhone; iOS 13.7; Scale/2.00)", 82 | "Accept-Language": "zh-Hans-CN;q=1, en-CN;q=0.9", 83 | } 84 | } 85 | return self.compact() 86 | 87 | def getKey(self, t): 88 | for s in range(0, 8): 89 | e = 1 & t 90 | t >>= 1 91 | e <<= 31 92 | t += e 93 | return t ^ 185025305 94 | -------------------------------------------------------------------------------- /qito/parse/video/mgtv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : mgtv.py 5 | @Time : 2022/8/30 上午2:08 6 | """ 7 | import template 8 | 9 | 10 | class Main(template.Template): 11 | def __init__(self): 12 | super().__init__() 13 | self.title = "芒果视频(MGTV)" 14 | 15 | def query(self): 16 | p = self.params 17 | if self.hasurl(p["parse"]): 18 | vid = self.match("\/(\d+)\.", p["parse"]) 19 | else: 20 | vid = p["parse"] 21 | 22 | if p.get("query"): 23 | url = f"https://pcweb.api.mgtv.com/video/info?vid={vid}" 24 | html = self.curl(url) 25 | self.logging.debug(f"getInfo: {html} \r\n") 26 | json = self.loads(html) 27 | if self.haskey(json, "data.info"): 28 | info = json["data"]["info"] 29 | title = info["videoName"] 30 | image = info["videoImage"] 31 | duration = self.seconds(info["time"]) 32 | return self.compact() 33 | 34 | def parse(self): 35 | p = self.params 36 | 37 | timestamp = self.timestamp 38 | assert p["vid"], "vid" 39 | vid = p["vid"] 40 | 41 | params = { 42 | "abroad": "0", 43 | "appVersion": "5.5.1", 44 | "clipId": "", 45 | "device": "iPhone", 46 | "dname": "abcdefg", 47 | "guid": "898839756384243712", 48 | "keepPlay": "1", 49 | "localPlayVideoId": "0", 50 | "localVideoWatchTime": "0", 51 | "mac": "e4b87ad76c79eb1183bfffcbcda3887ea6e819e2", 52 | "osType": "ios", 53 | "osVersion": "9.3.2", 54 | "seqId": "436ee82f7f5027eb051c7f402235d1b8", 55 | "source": "1", 56 | "ticket": "", 57 | "videoId": vid, 58 | } 59 | url = "http://mobile-bjyg.api.mgtv.com/v7/video/getSource?{}".format( 60 | self.urlencode(params) 61 | ) 62 | 63 | html = self.curl( 64 | { 65 | "url": "http://mobile-bjyg.api.mgtv.com/v7/video/getSource", 66 | "params": params, 67 | "headers": { 68 | "useragent": "MGTV-iPhone-appstore/5.5.1 (iPhone; iOS 9.3.2; Scale/2.00)", 69 | "encoding": "utf8", 70 | }, 71 | } 72 | ) 73 | self.logging.debug(f"getSource: {html} \r\n") 74 | data = self.loads(html) 75 | 76 | 77 | assert data["data"], data["msg"] 78 | 79 | adparams = self.loads(data["data"]["adParams"]) 80 | 81 | pay = adparams["v"]["ispay"] 82 | title = data["data"]["videoName"] 83 | duration = data["data"]["time"] 84 | 85 | stream = data["data"]["videoSources"] 86 | vodList = [i for i in stream if i["url"]] 87 | column = self.column(vodList, "", "name") 88 | lists = list(column.values())[::-1] 89 | assert lists, "lists" 90 | quality = list(column.keys())[::-1] 91 | show = self.data(quality, p["hd"]) 92 | vodDict = column[show] 93 | 94 | hlsUrl = f'https://disp.titan.mgtv.com{vodDict["url"]}&ver=0.2.21092&chk=b1e323d526786a667cf809f2e4bb2e25&guid=898839756384243712' 95 | size = int(vodDict["fileSize"]) 96 | if pay: 97 | hlsUrl = self.sub("arange=\d+", "arange=0", hlsUrl) 98 | m3u8 = "" 99 | for z in range(3): 100 | hlsSource = self.curl( 101 | { 102 | "url": hlsUrl, 103 | "headers": { 104 | "useragent": "MGTV-iPhone-appstore/5.5.1 (iPhone; iOS 9.3.2; Scale/2.00)", 105 | "encoding": "utf8", 106 | }, 107 | } 108 | ) 109 | hlsJson = self.loads(hlsSource) 110 | if hlsJson["info"].startswith("http"): 111 | m3u8 = hlsJson["info"] 112 | break 113 | self.logging.debug(f"getM3u8: {hlsSource} \r\n") 114 | assert m3u8, "m3u8" 115 | extra = { 116 | "headers": { 117 | "Accept-Language": "zh-cn", 118 | "X-Playback-Session-Id": "D11C5818-6A9D-401F-9F8E-E1093AFA2A46", 119 | "Accept": "*/*", 120 | "User-Agent": "AppleCoreMedia/1.0.0.16E227 (iPhone; U; CPU OS 12_2 like Mac OS X; zh_cn)", 121 | "Referer": m3u8, 122 | "Pragma": "no-cache", 123 | "Cache-Control": "no-cache", 124 | }, 125 | } 126 | 127 | ext = playback = "m3u8" 128 | 129 | return self.compact() 130 | 131 | def encode_tk2(self, s): 132 | string = self.self.replace( 133 | ["+", "/", "="], ["_", "~", "-"], self.self.b64encode(s) 134 | ) 135 | return string[::-1] 136 | -------------------------------------------------------------------------------- /qito/parse/video/miguvideo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : miguvideo.py 5 | @Time : 2022/11/14 下午4:18 6 | """ 7 | import template 8 | 9 | 10 | class Main(template.Template): 11 | def __init__(self): 12 | super().__init__() 13 | self.title = "咪咕视频(MIGUVIDEO)" 14 | self.appVersion = "2500090320" 15 | 16 | def query(self): 17 | p = self.params 18 | if p["parse"].startswith("http"): 19 | vid = self.match("cid=(\d+)", p["parse"]) 20 | else: 21 | vid = p["parse"] 22 | if p.get("query"): 23 | url = f"https://webapi.miguvideo.com/gateway/playurl/v3/play/playurlh5?contId={vid}&rateType=3&clientId=&startPlay=true&channelId=0132_10010001005" 24 | html = self.curl(url) 25 | self.logging.debug(f"getInfo: {html} \r\n") 26 | json = self.loads(html) 27 | if json["code"] == "200": 28 | duration = json["body"]["content"]["duration"] 29 | title = json["body"]["content"]["contName"] 30 | pay = 1 if json["body"]["urlInfo"]["urlType"] == "trial" else "" 31 | return self.compact() 32 | 33 | def parse(self): 34 | p = self.params 35 | assert p["vid"], "vid" 36 | vid = p["vid"] 37 | self.salt = f"{str(self.timestamp)[4:10]}96" 38 | rates = [2, 3, 4, 7] if self.cookie else [2, 3] 39 | url = f"https://play.miguvideo.com/playurl/v1/play/playurl?audio=false&contId={vid}&dolby=true&drm=true&flvEnable=false&h265=true&isMultiView=true&isRaming=0&isbox=false&nt=4&os=15.1.1&ott=false&rateType={self.data(rates,p['hd'])}&salt={self.salt}&serialNo=0&sign={self.getSign(vid)}&startPlay=true×tamp={self.timestamp}&ua=iPhone13%2C3&vivid=1&vr=true&xavs2=true&xh265=true" 40 | 41 | html = self.curl( 42 | {"url": url, "encoding": "utf8", "headers": {"appVersion": "2500090310"}} 43 | ) 44 | self.logging.debug(f"getPlayData: {html} \r\n") 45 | json = self.loads(html) 46 | assert json["code"] == "200", "data" 47 | duration = json["body"]["content"]["duration"] 48 | title = json["body"]["content"]["contName"] 49 | pay = "1" if json["body"]["urlInfos"][0]["urlType"] == "trial" else "" 50 | urlInfos = self.column(json["body"]["urlInfos"], "", "rateDesc") 51 | assert len(urlInfos) > 0, "lists" 52 | quality = list(urlInfos.keys())[::-1] 53 | show = self.data(quality, p["hd"]) 54 | info = urlInfos[show] 55 | size = int(info["mediaSize"]) 56 | m3u8 = info["url"] 57 | segs = [{"url": m3u8, "duration": duration}] 58 | playback = "m3u8" 59 | ext="hls" 60 | return self.compact() 61 | 62 | def getSign(self, contId): 63 | tm = self.timestamp 64 | md5string = self.md5(f"{tm}{contId}{self.appVersion[:8]}") 65 | sign = self.md5( 66 | f"{md5string}9100fcd3470f4c0f88b403f12eaaf65amigu{self.salt[:4]}" 67 | ) 68 | return sign 69 | -------------------------------------------------------------------------------- /qito/parse/video/pptv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : pptv.py 5 | @Time : 2022/10/10 下午8:36 6 | """ 7 | import template 8 | 9 | 10 | class Main(template.Template): 11 | def __init__(self): 12 | super().__init__() 13 | self.title = "PP视频(PPTV)" 14 | 15 | def query(self): 16 | p = self.params 17 | if p["parse"].isdigit(): 18 | vid = p["parse"] 19 | elif p["parse"].isalnum(): 20 | url = f"http://v.pptv.com/show/{p['parse']}.html" 21 | else: 22 | url = p["parse"] 23 | if url: 24 | tvid = self.match("show\/(\w+)", url) 25 | html = self.curl(url) 26 | # vid = self.match(['video_id="(\d+)"', '"id"\s*:\s*(\d+)', "id=(\d+)"], html) 27 | 28 | vid = self.match('"(?:c|ps)id":"?(\d+)', html) 29 | title = self.match( 30 | ['

(.*?)<\/h3>', '"title"\s*:\s*"(.*?)"'], html 31 | ) 32 | 33 | if not vid: 34 | vid = self.match("\/vod\/(\d+)\/", p["parse"]) 35 | 36 | assert vid, "vid" 37 | 38 | if p.get("query"): 39 | webParams = { 40 | "platform": "atv", 41 | "canal": "9122", 42 | "ver": "3", 43 | "lang": "zh_CN", 44 | "type": "ppbox.launcher.vip", 45 | "gslbversion": "2", 46 | "userLevel": "0", 47 | "open": "0", 48 | "content": "need_drag", 49 | "zone": "8", 50 | "pid": "5701", 51 | "vvid": "a7c64007-b0a8-662c-1853-a29913676ca2", 52 | "version": "4", 53 | "username": "", 54 | "ppi": "302c3630", 55 | "salt": "pv", 56 | "segment": "a72e242e_a72e2676_1488784198", 57 | "o": "0", 58 | "sl": "1", 59 | "referrer": "", 60 | "pageUrl": "http://v.pptv.com/show/IZlEw10VDEqtK5M.html?rcc_src=B3", 61 | "duration": "1242", 62 | "r": "1488786625410", 63 | "scver": "1ebf7a076b88f0bc4efbdde483a91d104-2c05-1516711145&bppcataid=94", 64 | } 65 | webUrl = f"https://web-play.pptv.com/webplay3-0-{vid}.xml" 66 | content = self.curl(webUrl, {"params": webParams}) 67 | self.logging.debug(f"getXmlInfo: {content} \r\n") 68 | 69 | title = self.match('nm="(.*?)"\s*', content) 70 | duration = self.match('dur="(\d+)"', content) 71 | 72 | rids = self.matchAll('rid="([^"\']+).mp4"\s*bitrate', content) 73 | fts = self.matchAll('ft="(\d+)"', content) 74 | 75 | if fts and fts[0] != "0": 76 | rids.reverse() 77 | context = {"rids": rids} 78 | return self.compact() 79 | 80 | def parse(self): 81 | p = self.params 82 | assert p["vid"], "vid" 83 | vid = p["vid"] 84 | image = f"http://s1.pplive.cn/v/cap/{vid}/w640.jpg" 85 | wtype = "mhpptv" 86 | webParams = [ 87 | { 88 | "platform": "atv", 89 | "canal": "9122", 90 | "ver": "3", 91 | "lang": "zh_CN", 92 | "type": wtype, 93 | "gslbversion": "2", 94 | "userLevel": "0", 95 | "open": "0", 96 | "content": "need_drag", 97 | "zone": "8", 98 | "pid": "5701", 99 | "vvid": "a7c64007-b0a8-662c-1853-a29913676ca2", 100 | "version": "4", 101 | "username": "", 102 | "ppi": "302c3630", 103 | "salt": "pv", 104 | "segment": "a72e242e_a72e2676_1488784198", 105 | "o": "0", 106 | "sl": "1", 107 | "referrer": "", 108 | "pageUrl": "http://v.pptv.com/show/IZlEw10VDEqtK5M.html?rcc_src=B3", 109 | "duration": "1242", 110 | "r": "1488786625410", 111 | "scver": "1ebf7a076b88f0bc4efbdde483a91d104-2c05-1516711145&bppcataid=94", 112 | "https": "true", 113 | }, 114 | { 115 | "zone": "8", 116 | "pid": "5701", 117 | "vvid": "8302d118-c059-7770-eb8d-23578916ca2d", 118 | "version": "4", 119 | "username": "", 120 | "ppi": "302c3333", 121 | "type": wtype, 122 | "pageUrl": "http://v.pptv.com/show/tVZZ1j6kFFK1M5s.html", 123 | "o": "0", 124 | "referrer": "", 125 | "kk": "75247faf091ef78c60173239f4b0e8ed-5fe5-5a674f72", 126 | "sl": "1", 127 | "duration": "215", 128 | "r": "1516716394945", 129 | "scver": "1", 130 | "appplt": "flp", 131 | "appid": "pptv.flashplayer.vod", 132 | "appver": "3.4.3.3", 133 | "nddp": "1", 134 | "https": "true", 135 | }, 136 | ] 137 | for i in webParams: 138 | url = f"https://web-play.pptv.com/webplay3-0-{vid}.xml" 139 | html = self.curl({"url": url, "params": i}) 140 | if ".mp4" in html: 141 | break 142 | self.logging.debug(f"getXmlSource: {html} \r\n") 143 | # 如果出现message,可能是vid错误或者视频删除/隐藏 144 | message = self.match('code="(\d+)"\s*message="([^"]+)"', html) 145 | 146 | if message and message[0] != "301": 147 | raise NotImplementedError("hide") 148 | 149 | rids = self.matchAll('rid="([^"]+).mp4"\s*bitrate', html) 150 | fts = self.matchAll('ft="(\d+)"', html) 151 | 152 | if fts and fts[0] != "0": 153 | rids.reverse() 154 | 155 | ary = ( 156 | ["流畅", "标清", "高清", "超清", "蓝光", "原画"] 157 | if len(rids) > 4 158 | else ["流畅", "高清", "超清", "蓝光"] 159 | ) 160 | 161 | quality = ary[: len(rids)] 162 | rid = self.data(rids, p["hd"]) 163 | show = self.data(quality, p["hd"]) 164 | dt_lists = self.matchAll("", html, "S") 165 | if fts and fts[0] != "0": 166 | dt_lists.reverse() 167 | dt = self.data(dt_lists, p["hd"]) 168 | 169 | # 获取资源的rid sh key相关信息 170 | info = self.matchAll( 171 | 'rid="([^"]+)".*?([^\<]+)<\/sh>.*?([^\<]+)<\/key>', dt, "S" 172 | ) 173 | match = self.matchAll( 174 | "<(\w+)[^\>]*>([^\<]+)<\/\w+>", dt.replace("", "") 175 | ) 176 | ch = "" # rid包含汉字 177 | 178 | if match: 179 | dicts = dict(match) 180 | sh = dicts["sh"] 181 | filename = self.match('rid="([^"]+)"', dt) 182 | key = dicts["key"] 183 | elif len(info) > 0: 184 | sh = info[0][1] 185 | key = info[0][2] 186 | filename = info[0][0] 187 | else: 188 | info = self.match("([^\<]+)<\/sh>.*?([^\<]+)<\/key>", dt, "S") 189 | sh = info[0] 190 | key = info[1] 191 | filename = rid + ".mp4" 192 | ch = "1" 193 | 194 | title = self.match('nm="(.*?)"\s*', html) 195 | 196 | duration = int(self.match('dur="(\d+)"', html)) 197 | m3u8 = "https://{}/{}?type={}&k={}".format( 198 | sh, filename.replace(".mp4", ".m3u8"), wtype, key 199 | ) 200 | ext = playback = "m3u8" 201 | try: 202 | size = int( 203 | self.data(self.matchAll(' 0: 113 | d.append(j) 114 | if self.haskey(data, "data.urls.m3u8.%s" % j): 115 | if len(data["data"]["urls"]["m3u8"][j]) > 0: 116 | e.append(j) 117 | if j in data["data"]["durations"]: 118 | if len(data["data"]["durations"][j]) > 0: 119 | f.append(j) 120 | assert d, "lists" 121 | quality = d 122 | show = self.data(quality, p["hd"]) 123 | mp4Data = data["data"]["urls"]["mp4"][show] 124 | durData = data["data"]["durations"][show] 125 | 126 | try: 127 | bt = p["context"]["bytes"][show].split(",") 128 | for i in tuple(zip(mp4Data, durData, bt)): 129 | segs.append( 130 | { 131 | "url": self.replace( 132 | "\d+\.\d+\.\d+\.\d+", "data.vod.itc.cn", i[0] 133 | ), 134 | "duration": i[1], 135 | "size": int(i[2]), 136 | } 137 | ) 138 | 139 | except: 140 | for i in tuple(zip(mp4Data, durData)): 141 | segs.append( 142 | { 143 | "url": self.sub("\d+\.\d+\.\d+\.\d+", "data.vod.itc.cn", i[0]), 144 | "duration": i[1], 145 | } 146 | ) 147 | m3u8 = ( 148 | data["data"]["urls"]["m3u8"][self.data(e, p["hd"])][0] + "&oth=&cd=&prod=h5" 149 | ) 150 | mp4 = ( 151 | data["data"]["urls"]["downloadUrl"][0][0] 152 | + "&qd=68001&src=11050001&ca=4&cateCode=101&_c=1&appid=tv&oth=&cd=&prod=h5" 153 | ) 154 | playback = "m3u8" 155 | # 可能存在上报出错,重新上报一次 156 | self.curl(reportUrl) 157 | extra = {"replace": ["http:", "https:"]} 158 | return self.compact() 159 | -------------------------------------------------------------------------------- /qito/parse/video/wasu.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : wasu.py 5 | @Time : 2022/11/21 下午3:48 6 | """ 7 | import template 8 | 9 | 10 | class Main(template.Template): 11 | def __init__(self): 12 | super().__init__() 13 | self.title = "()" 14 | 15 | def query(self): 16 | p = self.params 17 | if p["parse"].startswith("http"): 18 | vid = self.match(r"id\/(\d+)", p["parse"]) 19 | else: 20 | vid = p["parse"] 21 | html = self.curl( 22 | f"https://www.wasu.cn/Play/show/id/{vid}" 23 | ) 24 | image = self.match("_playpic\s*=\s*'([^']+)',", html) 25 | title = self.match("ali_vodName\s*=\s*'(.*?)',", html) 26 | duration = self.match("_playDuration\s*=\s*'([^']+)',", html) 27 | return self.compact() 28 | 29 | def parse(self): 30 | p = self.params 31 | assert p["vid"], "vid" 32 | vid = p["vid"] 33 | xml = self.curl( 34 | {"url": f"http://www.wasu.cn/Api/getPlayInfoById/id/{vid}/datatype/xml"} 35 | ) 36 | self.logging.debug(f"getXml: {xml} \r\n") 37 | videoList = self.matchAll( 38 | "\s*(\d+)<\/bitrate>\s*