├── N_m3u8DL-RE_Beta_win-x64 ├── N_m3u8DL-RE.exe └── ffmpeg.exe ├── README.md ├── RealListWindow.py ├── RealRid.py ├── RealTool.py ├── chromedriver.exe ├── config_ini.py ├── image ├── 73114636.png ├── 73114636_bg.png ├── close_icon.png ├── close_icon_2.png ├── close_icon_3.png ├── copy.png ├── icon.ico ├── icon.png ├── image_1.jpg ├── image_2.jpg ├── image_3.jpg ├── image_4.jpg ├── logo.png ├── logo_get.png ├── lol_s12.png ├── min_icon.png ├── play.png ├── rec.png ├── save.png ├── setting_icon.png └── size_icon.png ├── qtbase_zh_CN.qm └── real ├── bili.py ├── douyin.py ├── douyu.py ├── huya.py ├── kugou.py ├── kuwo.py ├── requests_code.py ├── rquests_html_1.py ├── yizhibo.py └── yy.py /N_m3u8DL-RE_Beta_win-x64/N_m3u8DL-RE.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s282730788/RealTool/33db6f408b476d1f2ac35bf7ef22b37e7a986dd5/N_m3u8DL-RE_Beta_win-x64/N_m3u8DL-RE.exe -------------------------------------------------------------------------------- /N_m3u8DL-RE_Beta_win-x64/ffmpeg.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s282730788/RealTool/33db6f408b476d1f2ac35bf7ef22b37e7a986dd5/N_m3u8DL-RE_Beta_win-x64/ffmpeg.exe -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RealTool 2 | 3 | 4 | ## 软件声明 5 | 此工具语言为Python【3.8.10】,GUI是PyQt5【PyQt5==5.14.2】,打包是用pyinstaller,360可能会报毒,所有设置都保存在本地,介意勿下! 6 | 蓝奏网盘:https://wwjd.lanzout.com/b00w74arg 密码:1234 7 | 8 | ## 关于RealTool 9 | RealTool是一款获取直播源链接的软件,支持获取【**斗鱼**】【**虎牙**】【**B站**】【**抖音**】【**YY**】【**酷我**】【**酷狗**】【**一直播**】的直播源链接,并且支持录制等功能! 10 | 有任何问题可以发Issues,也可以加入聊天群【1134070718】进行问题反馈! 11 | 12 | ## 关于开源 13 | 本软件的获取链接是基于两位大佬的开源项目 14 | 1. 获取直播源开源链接wbt5:https://github.com/wbt5/real-url 15 | 2. m3u8录制开源链接nilaoda:https://github.com/nilaoda/N_m3u8DL-RE 16 | 17 | 18 | ### 2022年10月22日 19 | **修复虎牙斗鱼直播源无法正确获取的问题,这是上版本修改过程中出现的BUG,现已修复!** 20 | **新增批量获取直播源,需要先手动添加直播间,然后输入框为空的情况下点击获取或者回车即可批量获取!** 21 | 22 | ### 2022年10月24日 23 | **新增【YY直播】【酷我直播】【酷狗直播】** 24 | **新增多线程访问,现在能更快的获取到直播源了** 25 | 26 | ### 2022年10月24日14:34 27 | **修复同一平台批量获取只能获取到一个的问题** 28 | 29 | ### 2022年10月26日 30 | **新采用Nuitka打包,理论上运行速度和兼容版本更强,只有64位【以前都是Pyinstaller打包】** 31 | **修复批量获取只有第一个平台才能获取成功的问题** 32 | **修复抖音直播源画质问题** 33 | 34 | ### 2022年10月27日 35 | **新增获取主播昵称功能,获取列表和保存列表均已添加** 36 | 37 | ### 2022年10月29日 38 | **修复YY直播获取失败的问题** 39 | **调整虎牙画质获取方式,现在强制获取所有虎牙直播画质** 40 | 41 | ### 2022年10月31日 42 | **修复YY直播因正则匹配原因导致软件崩溃的问题** 43 | **新增斗鱼m3u8格式** 44 | **根据坛友反馈MPV播放器播放几个小时都不会出现直播源失效的问题,所以推荐使用MPV播放器** 45 | **[MPV播放器官网](https://mpv.io)** 46 | 47 | **MPV懒人包网址** 48 | **[MPV懒人包](https://github.com/hooke007/MPV_lazy/)** 49 | 50 | **修复YY直播首页直播房间号获取的问题** 51 | 52 | 53 | ### 2022年11月3日 54 | **新增【一直播】直播源获取** 55 | 56 | ### 2022年11月12日 57 | **新增【播放设置】,现在可以选择PotPlayer或者MPVPlayer播放器** 58 | 59 | ### 2022年11月13日 60 | **修复因目录有空格而导致打开目录错误的问题** 61 | 62 | ### 2023年5月10日 63 | **修复斗鱼、B站无法获取直播源的问题** 64 | 65 | 66 | **修复因链接有特殊字符而无法用mpv播放的问题** 67 | 68 | **【严禁mpv目录中有空格及特殊字符,否则将无法播放】** 69 | 70 | -------------------------------------------------------------------------------- /RealListWindow.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time:2022/10/15 5:41 3 | # @Author: 须尽欢 4 | # @File:RealListWindow.py 5 | # Software:PyCharm 6 | import re 7 | import sys 8 | from PyQt5.QtWidgets import QWidget, QPushButton, QLabel, QHBoxLayout, QVBoxLayout, QLineEdit, QScrollArea, QApplication 9 | from PyQt5.QtCore import Qt, QRect 10 | from PyQt5.Qt import QRegExp, QRegExpValidator 11 | from PyQt5 import QtWidgets, QtCore, QtGui 12 | import pyperclip 13 | import os 14 | import subprocess 15 | import win32api 16 | import requests 17 | import time 18 | from configobj import ConfigObj 19 | 20 | 21 | class ThreadRec(QtCore.QThread): 22 | def __init__(self, live_name, url_link, rid, url_name): 23 | super(ThreadRec, self).__init__() 24 | self.live_name = live_name 25 | self.url_link = url_link 26 | self.rid = rid 27 | self.url_name = url_name 28 | 29 | def run(self): 30 | path_down = '{}/real_save/{}/Downloads'.format(os.getcwd(), self.rid) 31 | if not os.path.exists(path_down): 32 | os.makedirs(path_down) 33 | 34 | if ".m3u8" in self.url_link: 35 | path_down.replace("/", "\\") 36 | live_url = self.url_link 37 | live_url = live_url.replace("&", "^&^") 38 | live_url = live_url.replace("^^", "^") 39 | rec = 'N_m3u8DL-RE_Beta_win-x64\\N_m3u8DL-RE.exe "{}" --tmp-dir "{}" --save-dir "{}" --save-name "{}_{}_{}" --live-real-time-merge --del-after-done'.format( 40 | live_url, path_down, path_down, self.live_name, self.rid, 41 | time.strftime("%Y-%m-%d_%H-%M-%S", time.localtime())) 42 | with open("rec.bat", "w", encoding="utf-8") as f: 43 | f.write(rec) 44 | win32api.ShellExecute(0, 'open', 'rec.bat', '', '', 1) # 前台打开 45 | 46 | elif ".flv" in self.url_link or ".xs" in self.url_link: 47 | try: 48 | response = requests.get(self.url_link, stream=True) 49 | f = open("{}/{}_{}_{}.flv".format(path_down, self.live_name, self.url_name, 50 | time.strftime("%Y-%m-%d_%H-%M-%S", time.localtime())), "ab") 51 | for chunk in response.iter_content(chunk_size=1024): 52 | if chunk: 53 | f.write(chunk) 54 | f.flush() 55 | else: 56 | f.close() 57 | except: 58 | pass 59 | 60 | 61 | class ButtonTitle(QtWidgets.QPushButton): 62 | def __init__(self, name, border_color, parent=None): 63 | super().__init__(parent) 64 | self.name = name 65 | self.setFixedSize(100, 33) 66 | self.setToolTip("全部保存") 67 | self.setStyleSheet("""QPushButton{ 68 | border-radius:0px; 69 | border-top-left-radius:10px; 70 | border-top-right-radius:10px; 71 | font-family:"Microsoft YaHei"; 72 | color:#ffffff; 73 | font-size:18px; 74 | font-weight: bold; 75 | background-color: %s; } 76 | }""" % border_color) 77 | self.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) # 鼠标指针变成手抓状 78 | self.setText(name) 79 | 80 | def enterEvent(self, event): # 进入 81 | self.setText("保存") 82 | super().enterEvent(event) 83 | 84 | def leaveEvent(self, event): # 离开 85 | self.setText(self.name) 86 | super().leaveEvent(event) 87 | 88 | 89 | class PushButton(QPushButton): 90 | def __init__(self, background_img=None, background_color_rgb=None, background_color_rgba=None, duration=None, 91 | button_bg=None, pushbutton_stylesheet=None, parent=None): 92 | """ 93 | :param background_img:图片地址 94 | :param background_color_rgb:红绿蓝颜色 95 | :param background_color_rgba:透明度 0~1 96 | :param duration:持续时间 97 | :param button_bg:默认背景透明度0~1 98 | :param pushbutton_stylesheet:其余样式 99 | :param parent: 100 | """ 101 | super().__init__(parent) 102 | self.background_img = background_img 103 | self.background_color_rgb = background_color_rgb 104 | self.background_color_rgba = background_color_rgba 105 | self.button_bg = button_bg 106 | self.button_stylesheet = pushbutton_stylesheet 107 | 108 | self._animation = QtCore.QVariantAnimation( # 创建一个动画 109 | startValue=0, 110 | endValue=100, 111 | valueChanged=self._on_value_changed, # 更改值的函数 112 | duration=duration, 113 | ) 114 | self._update_stylesheet(self.button_bg) 115 | self.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) # 鼠标指针变成手抓状 116 | 117 | def _on_value_changed(self, a): # 更新 改变后的值 118 | a = a / 100 * self.background_color_rgba + self.button_bg 119 | self._update_stylesheet(a) # 更新并调用样式 120 | 121 | def _update_stylesheet(self, a): # 更新样式函数 122 | 123 | self.setStyleSheet("""QPushButton{background-image: url(%s); 124 | background-color:rgba(%s,%s); 125 | %s} 126 | 127 | """ % ( 128 | self.background_img, self.background_color_rgb, a, self.button_stylesheet)) 129 | 130 | def enterEvent(self, event): # 进入 131 | self._animation.setDirection(QtCore.QAbstractAnimation.Forward) # 正 132 | self._animation.start() 133 | super().enterEvent(event) 134 | 135 | def leaveEvent(self, event): # 离开 136 | self._animation.setDirection(QtCore.QAbstractAnimation.Backward) # 反 137 | self._animation.start() 138 | super().leaveEvent(event) 139 | 140 | 141 | class RoundShadow(QWidget): 142 | """圆角边框类""" 143 | 144 | def __init__(self, parent=None): 145 | super(RoundShadow, self).__init__(parent) 146 | self.border_width = 10 147 | # 设置 窗口无边框和背景透明 *必须 148 | self.setAttribute(Qt.WA_TranslucentBackground) 149 | self.setWindowFlags(Qt.FramelessWindowHint | Qt.Window) 150 | 151 | def paintEvent(self, event): 152 | # 阴影 153 | path = QtGui.QPainterPath() 154 | path.setFillRule(Qt.WindingFill) 155 | 156 | pat = QtGui.QPainter(self) 157 | pat.setRenderHint(pat.Antialiasing) 158 | pat.fillPath(path, QtGui.QBrush(Qt.white)) 159 | 160 | color = QtGui.QColor(192, 192, 192, 50) 161 | 162 | for i in range(10): 163 | i_path = QtGui.QPainterPath() 164 | i_path.setFillRule(Qt.WindingFill) 165 | ref = QtCore.QRectF(10 - i, 10 - i, self.width() - (10 - i) * 2, self.height() - (10 - i) * 2) 166 | # i_path.addRect(ref) 167 | i_path.addRoundedRect(ref, self.border_width, self.border_width) 168 | color.setAlpha(int(150 - i ** 0.5 * 50)) 169 | pat.setPen(color) 170 | pat.drawPath(i_path) 171 | 172 | # 圆角 173 | pat2 = QtGui.QPainter(self) 174 | pat2.setRenderHint(pat2.Antialiasing) # 抗锯齿 175 | pat2.setBrush(Qt.white) 176 | pat2.setPen(Qt.transparent) 177 | 178 | 179 | class MoveTop(QWidget): 180 | def __init__(self, parent): 181 | super(MoveTop, self).__init__() 182 | self.win = parent 183 | self.InitializeWindow() 184 | self.setStyleSheet(""" 185 | QWidget{ 186 | background-color:rgba(255,0,255,0); 187 | border:none; 188 | margin:0px; 189 | }""") 190 | 191 | def InitializeWindow(self): 192 | self.isPressed = False 193 | top = QLabel() 194 | layout_h = QHBoxLayout() 195 | layout_h.addWidget(top) 196 | self.setLayout(layout_h) 197 | 198 | def mousePressEvent(self, event): # 设置鼠标拖动Label 199 | if event.button() == Qt.LeftButton: # 如果鼠标按钮等于左键 200 | self.isPressed = True 201 | self.startPos = event.globalPos() 202 | return QWidget().mousePressEvent(event) 203 | 204 | def mouseReleaseEvent(self, event): 205 | if event.button() == Qt.LeftButton: # 如果鼠标按钮等于左键 206 | self.isPressed = False 207 | return QWidget().mouseReleaseEvent(event) 208 | 209 | def mouseMoveEvent(self, event): 210 | if self.isPressed: 211 | if self.win.isMaximized: 212 | self.win.showNormal() 213 | 214 | movePos = event.globalPos() - self.startPos 215 | self.startPos = event.globalPos() 216 | self.win.move(self.win.pos() + movePos) 217 | 218 | return QWidget().mouseMoveEvent(event) 219 | 220 | 221 | class RealList(RoundShadow, QWidget): 222 | _signal = QtCore.pyqtSignal(str) 223 | 224 | def __init__(self, real_dict, parent=None): 225 | super(RealList, self).__init__(parent) 226 | self.real_dict = real_dict 227 | self.setup_ui() 228 | 229 | def setup_ui(self): 230 | self.setWindowFlags( 231 | QtCore.Qt.Window | QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowMinimizeButtonHint | QtCore.Qt.WindowMaximizeButtonHint) # 隐藏标题栏且可以任务栏最小化 232 | self.setMinimumSize(QtCore.QSize(850, 610)) # 设置透明窗口最小尺寸 233 | self.setWindowTitle("直播源列表") 234 | self.setWindowIcon(QtGui.QIcon("./image/icon.png")) 235 | 236 | self.label_bottom = QtWidgets.QLabel(self) # 创建背景标签 237 | self.label_bottom.setGeometry(5, 60, 840, 540) 238 | self.label_bottom.setStyleSheet("""QLabel{ 239 | border:none; 240 | border-bottom:1px solid #E7E7E7; 241 | background-color:rgba(255,255,255,1); 242 | border-bottom-left-radius:5px; 243 | border-bottom-right-radius:5px; 244 | } 245 | """) 246 | self.button_close = PushButton("./image/close_icon_2.png", "0,0,0", 0, 200, 0, 247 | '''border:none;background-repeat:no-repeat;background-position:center;margin-right:0px;border-radius:5px;}QPushButton:hover{background-image:url(./image/close_icon_3.png);''') 248 | self.button_close.setObjectName("pushButton_close") 249 | self.button_close.setMinimumSize(QtCore.QSize(39, 39)) 250 | self.button_close.setMaximumSize(QtCore.QSize(39, 39)) 251 | self.button_close.setToolTip("关闭") 252 | 253 | self.label_title = QLabel() 254 | self.label_title.setFixedSize(100, 35) 255 | self.label_title.setAlignment(Qt.AlignCenter) 256 | self.label_title.setText("直播源列表") 257 | self.label_title.setStyleSheet("""QLabel{ 258 | border:none; 259 | border-radius:16px; 260 | font-family:"Microsoft YaHei"; 261 | font-weight:bold; 262 | text-align:center; 263 | color:#474747; 264 | font-size:16px; 265 | background-color:none;; 266 | } 267 | """) 268 | 269 | self.label_top = QLabel(self) 270 | self.label_top.setGeometry(QtCore.QRect(5, 0, 840, 60)) 271 | self.label_top.setStyleSheet(""" 272 | QLabel{ 273 | border-top:1px solid #E9E9E9; 274 | border-bottom:1px solid #E7E7E7; 275 | background-color:rgba(255,255,255,1); 276 | border-top-left-radius:5px; 277 | border-top-right-radius:5px; 278 | } 279 | """) 280 | 281 | self.move_top_left = MoveTop(self) 282 | self.move_top_left.setFixedWidth(310) 283 | self.move_top_left.setObjectName("move_top_title") 284 | self.move_top_right = MoveTop(self) 285 | self.move_top_right.setObjectName("move_top_title") 286 | 287 | self.hbox_top = QHBoxLayout(self.label_top) 288 | self.hbox_top.addWidget(self.move_top_left) 289 | self.hbox_top.addWidget(self.label_title, Qt.AlignHCenter | Qt.AlignVCenter) 290 | self.hbox_top.addWidget(self.move_top_right) 291 | self.hbox_top.addWidget(self.button_close, Qt.AlignRight) 292 | self.hbox_top.setContentsMargins(60, 0, 10, 0) # 内边距 293 | self.hbox_top.setSpacing(0) 294 | 295 | self.label_scroll = QLabel(self.label_bottom) 296 | self.label_scroll.setMinimumSize(840, 500) 297 | self.label_scroll.setStyleSheet("""QLabel{ 298 | border-bottom-left-radius:5px; 299 | border-bottom-right-radius:5px; 300 | }""") 301 | 302 | self.scroll = QScrollArea() # 创建一个滚动条 303 | self.scroll.setWidget(self.label_scroll) 304 | self.scroll.setGeometry(QRect(0, 0, 840, 500)) # 滚动条阈值 305 | self.scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) # 不显示水平滚动条 306 | self.scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) # 不显示垂直滚动条 307 | self.scroll.setStyleSheet(""" 308 | QScrollArea{ 309 | border-bottom-left-radius:5px; 310 | border-bottom-right-radius:5px; 311 | background-color:#ffffff; 312 | border-image:url("./image/73114636_bg.png"); 313 | padding:0px 0px 5px 0px; 314 | } 315 | """) 316 | 317 | self.add_layout() 318 | self.vbox_widget = QVBoxLayout(self) 319 | self.vbox_widget.addLayout(self.hbox_top) 320 | self.vbox_widget.addWidget(self.scroll) 321 | self.vbox_widget.setContentsMargins(5, 60, 5, 10) # 内边距 322 | self.vbox_widget.setSpacing(0) 323 | self.button_close.clicked.connect(self.closes) 324 | 325 | def label_text(self, text): 326 | self.label_title.setText(text) 327 | self.label_text_animation = QtCore.QVariantAnimation( # 创建一个动画 328 | startValue=0, 329 | endValue=100, 330 | valueChanged=self._update_stylesheet, # 更改值的函数 331 | duration=1500, 332 | ) 333 | self.label_text_animation.start() 334 | 335 | def _update_stylesheet(self, a): # 更新样式函数 336 | b = 1 - (100 - (a * 2)) / 100 337 | if b >= 1: 338 | b = (100 - a) / 50 339 | if b <= 0: 340 | self.label_title.setText("直播源列表") 341 | b = 1 342 | self.label_title.setStyleSheet("""QLabel{ 343 | border:none; 344 | border-radius:16px; 345 | font-family:"Microsoft YaHei"; 346 | font-weight:bold; 347 | text-align:center; 348 | color:rgba(71,71,71, %s); 349 | font-size:16px; 350 | background-color:none;; 351 | } 352 | """ % b) 353 | 354 | def add_layout_title_save_all(self, live_name, url_dict, border_color, rid, 355 | nick_name): # 平台名称,链接字典,border颜色,rid, 用户名称 356 | 357 | def save(url_link, url_name, count): 358 | save_path = "{}/real_save/{}".format(os.getcwd(), rid) 359 | if not os.path.exists("{}/real_save/{}".format(os.getcwd(), rid)): 360 | os.makedirs( 361 | "{}/real_save/{}".format(os.getcwd(), rid)) 362 | asx_path = "{}/{}_{}_{}_{}.asx".format(save_path, live_name, url_name, count, nick_name) 363 | if url_link: 364 | asx_str = """ 365 | 366 | 367 | {}_{}_{} 368 | 369 | 370 | 371 | """.format(live_name, url_name, nick_name, url_link) 372 | with open(asx_path, "w", encoding="UTF8") as f: 373 | f.write(asx_str) 374 | 375 | def save_all(): 376 | for count, real_one in enumerate(url_dict): 377 | for url_name in real_one: 378 | save(real_one[url_name], url_name, count) 379 | self.label_text("全部保存成功") 380 | 381 | button_title_save_all = ButtonTitle(live_name, border_color) 382 | button_title_save_all.clicked.connect(save_all) 383 | 384 | label_rid = QLabel() 385 | label_rid.setText(rid) 386 | label_rid.setFixedHeight(33) 387 | label_rid.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) 388 | label_rid.setStyleSheet("""QLabel{ 389 | color:%s; 390 | font-size:18px; 391 | font-family:"Microsoft YaHei"; 392 | font-weight: bold; 393 | }""" % border_color) 394 | 395 | label_name = QLabel() 396 | label_name.setText(nick_name) 397 | label_name.setFixedWidth(400) 398 | label_name.setAlignment(Qt.AlignRight | Qt.AlignVCenter) 399 | label_name.setStyleSheet("""QLabel{ 400 | color:%s; 401 | font-size:18px; 402 | font-family:"Microsoft YaHei"; 403 | font-weight: bold; 404 | }""" % border_color) 405 | 406 | hbox_title = QHBoxLayout() 407 | hbox_title.addWidget(button_title_save_all) 408 | hbox_title.addWidget(label_rid) 409 | hbox_title.addStretch() 410 | hbox_title.addWidget(label_name) 411 | hbox_title.setContentsMargins(0, 0, 20, 0) # 内边距 412 | hbox_title.setSpacing(20) 413 | 414 | return hbox_title 415 | 416 | def add_layout_url(self, live_name, url_name, url_link, border_color, count, rid, 417 | nick_name): # 平台名称,链接名称, 链接地址, border颜色,链接计数,用户名称 418 | label_name = QLabel() 419 | label_name.setText(url_name) 420 | label_name.setFixedSize(160, 32) 421 | label_name.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) 422 | label_name.setStyleSheet("""QLabel{ 423 | border:none; 424 | border-radius:5px; 425 | border-bottom-right-radius:0px; 426 | font-family:"Microsoft YaHei"; 427 | color:#ffffff; 428 | font-size:16px; 429 | background-color: %s; 430 | }""" % border_color) 431 | lineedit_url = QLineEdit() 432 | lineedit_url.setFixedSize(600, 32) 433 | lineedit_url.setText(url_link) 434 | lineedit_url.setStyleSheet("""QLineEdit{ 435 | padding:0px 150px 0px 10px; 436 | border:none; 437 | font-family:"Microsoft YaHei"; 438 | font-size:16px; 439 | border-bottom:1px solid %s; 440 | color:#4C4B50; 441 | text-align:left; 442 | background-color:rgba(255,255,255,0); 443 | } 444 | QLineEdit:hover{ 445 | border-bottom:2px solid %s;} 446 | QLineEdit:focus{ 447 | border-bottom:2px solid %s;} 448 | """ % (border_color, border_color, border_color)) 449 | rx = QRegExp("[\S]*") 450 | validator = QRegExpValidator() 451 | validator.setRegExp(rx) 452 | lineedit_url.setValidator(validator) # 设置屏蔽空格 453 | 454 | def button_qss(image): 455 | button_qss = """QPushButton{ 456 | border: 2px solid %s; 457 | border-radius:5px; 458 | background-color:%s; 459 | border-image:url(./image/%s); 460 | } 461 | QPushButton:hover{ 462 | border:0px solid %s; 463 | }""" % (border_color, border_color, image, border_color) 464 | return button_qss 465 | 466 | def copy_(): 467 | pyperclip.copy(url_link) 468 | self.label_text("复制成功") 469 | 470 | button_copy = QPushButton() 471 | button_copy.setFixedSize(28, 28) 472 | button_copy.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 473 | button_copy.setToolTip('复制') 474 | button_copy.setStyleSheet(button_qss('copy.png')) 475 | button_copy.clicked.connect(copy_) 476 | 477 | button_save = QPushButton() 478 | button_save.setFixedSize(28, 28) 479 | button_save.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 480 | button_save.setToolTip('保存') 481 | button_save.setStyleSheet(button_qss('save.png')) 482 | button_save.clicked.connect(lambda: asx_("save", rid)) 483 | 484 | button_play = QPushButton() 485 | button_play.setFixedSize(28, 28) 486 | button_play.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 487 | button_play.setToolTip('播放') 488 | button_play.setStyleSheet(button_qss('play.png')) 489 | button_play.clicked.connect(lambda: player_()) 490 | 491 | button_rec = QPushButton() 492 | button_rec.setFixedSize(28, 28) 493 | button_rec.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 494 | button_rec.setToolTip('录制') 495 | button_rec.setStyleSheet(button_qss('rec.png')) 496 | button_rec.clicked.connect(lambda: asx_("rec", rid)) 497 | 498 | def player_(): 499 | config = ConfigObj("config.ini", encoding='UTF8') 500 | if config['player'] == 'pot': 501 | asx_('play', rid) 502 | elif config['player'] == 'mpv': 503 | url_link_ = '\"%s\"' % url_link 504 | mpv_path = config['mpv'] 505 | subprocess.Popen('cmd.exe /C %s %s' % (mpv_path, url_link_), shell=True) 506 | 507 | def asx_(text, rid_): 508 | save_path = "{}/real_save/{}".format(os.getcwd(), rid_) 509 | if not os.path.exists("{}/real_save/{}".format(os.getcwd(), rid_)): 510 | os.makedirs( 511 | "{}/real_save/{}".format(os.getcwd(), rid_)) 512 | 513 | asx_path = "{}/{}_{}_{}_{}.asx".format(save_path, live_name, url_name, count, nick_name) 514 | 515 | def save(): 516 | if url_link: 517 | asx_str = """ 518 | 519 | 520 | {}_{}_{} 521 | 522 | 523 | 524 | """.format(live_name, url_name, nick_name, url_link) 525 | 526 | with open(asx_path, "w", encoding="UTF8") as f: 527 | f.write(asx_str) 528 | 529 | if text == "save": 530 | save() 531 | self.label_text("保存成功") 532 | elif text == "play": 533 | save() 534 | os.startfile(asx_path) 535 | self.label_text("打开文件") 536 | elif text == 'rec': 537 | self.thread_rec = ThreadRec(live_name, url_link.strip(), rid_, url_name) 538 | self.thread_rec.start() 539 | self.label_text("正在录制...") 540 | button_rec.setStyleSheet("""QPushButton{ 541 | border:0px solid #20D68D; 542 | border-radius:5px; 543 | background-color:#20D68D; 544 | border-image:url(./image/rec); 545 | } 546 | """) 547 | 548 | hbox_tool = QHBoxLayout(lineedit_url) 549 | hbox_tool.addStretch() 550 | hbox_tool.addWidget(button_copy, Qt.AlignHCenter | Qt.AlignTop) 551 | 552 | hbox_tool.addWidget(button_save) 553 | hbox_tool.addWidget(button_play) 554 | hbox_tool.addWidget(button_rec) 555 | hbox_tool.setContentsMargins(0, 0, 0, 0) # 内边距 556 | hbox_tool.setSpacing(3) 557 | 558 | hbox_url = QHBoxLayout() 559 | hbox_url.addWidget(label_name) 560 | hbox_url.addWidget(lineedit_url) 561 | hbox_url.addStretch() 562 | hbox_url.setContentsMargins(0, 0, 0, 0) # 内边距 563 | hbox_url.setSpacing(0) 564 | return hbox_url 565 | 566 | def add_layout(self): 567 | if self.real_dict: 568 | vbox_real = QVBoxLayout(self.label_scroll) 569 | vbox_real.setSpacing(0) 570 | vbox_real.setContentsMargins(20, 0, 0, 0) # 内边距 571 | real_height = 0 572 | border_color = '#FF7701' 573 | for name in self.real_dict: 574 | if "douyu" in name: 575 | border_color = '#FF7701' 576 | elif "bili" in name: 577 | border_color = '#00AEEC' 578 | elif 'douyin' in name: 579 | border_color = '#FE2B54' 580 | elif 'huya' in name: 581 | border_color = '#FFAB08' 582 | elif 'yy' in name: 583 | border_color = '#FFE600' 584 | elif 'kuwo' in name: 585 | border_color = '#CB83F7' 586 | elif 'kugou' in name: 587 | border_color = '#01D07F' 588 | elif 'yizhibo' in name: 589 | border_color = '#2B2C2E' 590 | 591 | vbox_list = QVBoxLayout() 592 | vbox_list.setSpacing(0) 593 | hbox_title = QHBoxLayout() 594 | real_height += 62 595 | list_height = 0 596 | label_url_background = QLabel() 597 | label_url_background.setStyleSheet("""QLabel{ 598 | border:2px solid %s; 599 | border-top-right-radius:5px; 600 | border-bottom-left-radius:5px; 601 | border-bottom-right-radius:5px; 602 | }""" % border_color) 603 | 604 | hbox_title.addLayout(self.add_layout_title_save_all(name, self.real_dict[name], border_color, 605 | self.real_dict[name][-1]['rid'], 606 | self.real_dict[name][-2]['name'])) 607 | vbox_url = QVBoxLayout(label_url_background) 608 | for count, url_dict in enumerate(self.real_dict[name]): 609 | real_height += 80 610 | list_height += 80 611 | label_url_background.setFixedSize(800, list_height) 612 | vbox_list.addLayout(hbox_title) 613 | vbox_list.addWidget(label_url_background) 614 | vbox_list.setContentsMargins(0, 30, 0, 0) # 内边距 615 | vbox_list.setSpacing(0) 616 | for url_name in url_dict: 617 | if url_name != 'rid' and url_name != 'name': 618 | vbox_url.addLayout( 619 | self.add_layout_url(name, url_name, url_dict[url_name], border_color, count, 620 | self.real_dict[name][-1]['rid'], self.real_dict[name][-2]['name'])) 621 | self.label_scroll.setMinimumHeight(real_height) 622 | vbox_real.addLayout(vbox_list) 623 | vbox_real.addStretch() 624 | 625 | def add_text_(self, text): 626 | self._signal.emit(text) 627 | 628 | def closes(self): # 关闭窗口 629 | self.close() 630 | 631 | 632 | if __name__ == '__main__': 633 | real_dict_ = {'douyu': [{'原画_flv': 'http://ws-tct.douyucdn.cn/live/6rdygfhd.flv?uuid='}, 634 | {'原画_m3u8': 'http://ws-tct.douyucdn.cn/live/6rdygfhd.m3u8?uuid='}, 635 | {'原画_x_p2p': 'http://ws-tct.douyucdn.cn/live/6rdygfhd.xs?uuid='}, 636 | {'高清_flv': 'http://ws-tct.douyucdn.cn/live/6rdygfhd_2000.flv?uuid='}, 637 | {'高清_m3u8': 'http://ws-tct.douyucdn.cn/live/6rdygfhd_2000.m3u8?uuid='}, 638 | {'高清_x_p2p': 'http://ws-tct.douyucdn.cn/live/6rdygfhd_2000.xs?uuid='}, {'name': '斗鱼官方直播'}, 639 | {'rid': '6'}], 'bili': [{ 640 | '线路1_10000': 'https://d1--cn-gotcha208.bilivideo.com/live-bvc/779788/live_50329118_9516950_bluray/index.m3u8?expires=1668276677&len=0&oi=1996077206&pt=web&qn=10000&trid=1007920b1c6ee6d349bea1cfd76324335aac&sigparams=cdn,expires,len,oi,pt,qn,trid&cdn=cn-gotcha208&sign=606162f239f4bb86e617278699a75412&sk=c9c6154426932efa80d25af02e87a3bd&p2p_type=1&src=57345&sl=10&free_type=0&pp=rtmp&machinezone=ylf&source=onetier&site=41084a238fdf53253ad6d6924866b8ea&order=1'}, 641 | { 642 | '线路2_10000': 'https://cn-hbyc-ct-02-28.bilivideo.com/live-bvc/779788/live_50329118_9516950_bluray/index.m3u8?expires=1668276677&len=0&oi=1996077206&pt=web&qn=10000&trid=1007920b1c6ee6d349bea1cfd76324335aac&sigparams=cdn,expires,len,oi,pt,qn,trid&cdn=cn-gotcha01&sign=c2792b36c2e5151db1bde3b257d5e1a3&sk=c9c6154426932efa80d25af02e87a3bd&flvsk=2935686d6cb9146c7a6a6a0b4e120e2557adc0b48de99725a5887e843ee476a1&p2p_type=1&src=57345&sl=10&free_type=0&sid=cn-hbyc-ct-02-28&chash=0&sche=ban&bvchls=1&score=18&pp=rtmp&machinezone=ylf&source=onetier&site=41084a238fdf53253ad6d6924866b8ea&order=2'}, 643 | { 644 | '线路2_2500': 'https://cn-hbyc-ct-02-28.bilivideo.com/live-bvc/779788/live_50329118_9516950_2500/index.m3u8?expires=1668276677&len=0&oi=1996077206&pt=web&qn=2500&trid=1007920b1c6ee6d349bea1cfd76324335aac&sigparams=cdn,expires,len,oi,pt,qn,trid&cdn=cn-gotcha01&sign=c2792b36c2e5151db1bde3b257d5e1a3&sk=c9c6154426932efa80d25af02e87a3bd&flvsk=2935686d6cb9146c7a6a6a0b4e120e2557adc0b48de99725a5887e843ee476a1&p2p_type=1&src=57345&sl=10&free_type=0&sid=cn-hbyc-ct-02-28&chash=0&sche=ban&bvchls=1&score=18&pp=rtmp&machinezone=ylf&source=onetier&site=41084a238fdf53253ad6d6924866b8ea&order=2'}, 645 | { 646 | '线路2_1500': 'https://cn-hbyc-ct-02-28.bilivideo.com/live-bvc/779788/live_50329118_9516950_1500/index.m3u8?expires=1668276677&len=0&oi=1996077206&pt=web&qn=1500&trid=1007920b1c6ee6d349bea1cfd76324335aac&sigparams=cdn,expires,len,oi,pt,qn,trid&cdn=cn-gotcha01&sign=c2792b36c2e5151db1bde3b257d5e1a3&sk=c9c6154426932efa80d25af02e87a3bd&flvsk=2935686d6cb9146c7a6a6a0b4e120e2557adc0b48de99725a5887e843ee476a1&p2p_type=1&src=57345&sl=10&free_type=0&sid=cn-hbyc-ct-02-28&chash=0&sche=ban&bvchls=1&score=18&pp=rtmp&machinezone=ylf&source=onetier&site=41084a238fdf53253ad6d6924866b8ea&order=2'}, 647 | {'name': '哔哩哔哩英雄联盟赛事'}, {'rid': '6'}]} 648 | 649 | app = QApplication(sys.argv) 650 | favorites = RealList(real_dict_) 651 | favorites.show() 652 | sys.exit(app.exec_()) 653 | -------------------------------------------------------------------------------- /RealRid.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time:2022/10/21 9:35 3 | # @Author: Lanser 4 | # @File:RealRid.py 5 | # Software:PyCharm 6 | 7 | import sys 8 | from PyQt5.QtWidgets import QWidget, QPushButton, QLabel, QHBoxLayout, QVBoxLayout, QLineEdit, QScrollArea, QApplication 9 | from PyQt5.QtCore import Qt, QRect 10 | from PyQt5 import QtWidgets, QtCore, QtGui 11 | from configobj import ConfigObj 12 | import os 13 | import ast 14 | import re 15 | import requests 16 | 17 | 18 | class PushButton(QPushButton): 19 | def __init__(self, background_img=None, background_color_rgb=None, background_color_rgba=None, duration=None, 20 | button_bg=None, pushbutton_stylesheet=None, parent=None): 21 | """ 22 | :param background_img:图片地址 23 | :param background_color_rgb:红绿蓝颜色 24 | :param background_color_rgba:透明度 0~1 25 | :param duration:持续时间 26 | :param button_bg:默认背景透明度0~1 27 | :param pushbutton_stylesheet:其余样式 28 | :param parent: 29 | """ 30 | super().__init__(parent) 31 | self.background_img = background_img 32 | self.background_color_rgb = background_color_rgb 33 | self.background_color_rgba = background_color_rgba 34 | self.button_bg = button_bg 35 | self.button_stylesheet = pushbutton_stylesheet 36 | 37 | self._animation = QtCore.QVariantAnimation( # 创建一个动画 38 | startValue=0, 39 | endValue=100, 40 | valueChanged=self._on_value_changed, # 更改值的函数 41 | duration=duration, 42 | ) 43 | self._update_stylesheet(self.button_bg) 44 | self.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) # 鼠标指针变成手抓状 45 | 46 | def _on_value_changed(self, a): # 更新 改变后的值 47 | a = a / 100 * self.background_color_rgba + self.button_bg 48 | self._update_stylesheet(a) # 更新并调用样式 49 | 50 | def _update_stylesheet(self, a): # 更新样式函数 51 | 52 | self.setStyleSheet("""QPushButton{background-image: url(%s); 53 | background-color:rgba(%s,%s); 54 | %s} 55 | 56 | """ % ( 57 | self.background_img, self.background_color_rgb, a, self.button_stylesheet)) 58 | 59 | def enterEvent(self, event): # 进入 60 | self._animation.setDirection(QtCore.QAbstractAnimation.Forward) # 正 61 | self._animation.start() 62 | super().enterEvent(event) 63 | 64 | def leaveEvent(self, event): # 离开 65 | self._animation.setDirection(QtCore.QAbstractAnimation.Backward) # 反 66 | self._animation.start() 67 | super().leaveEvent(event) 68 | 69 | 70 | class RoundShadow(QWidget): 71 | """圆角边框类""" 72 | 73 | def __init__(self, parent=None): 74 | super(RoundShadow, self).__init__(parent) 75 | self.border_width = 10 76 | # 设置 窗口无边框和背景透明 *必须 77 | self.setAttribute(Qt.WA_TranslucentBackground) 78 | self.setWindowFlags(Qt.FramelessWindowHint | Qt.Window) 79 | 80 | def paintEvent(self, event): 81 | # 阴影 82 | path = QtGui.QPainterPath() 83 | path.setFillRule(Qt.WindingFill) 84 | 85 | pat = QtGui.QPainter(self) 86 | pat.setRenderHint(pat.Antialiasing) 87 | pat.fillPath(path, QtGui.QBrush(Qt.white)) 88 | 89 | color = QtGui.QColor(192, 192, 192, 50) 90 | 91 | for i in range(10): 92 | i_path = QtGui.QPainterPath() 93 | i_path.setFillRule(Qt.WindingFill) 94 | ref = QtCore.QRectF(10 - i, 10 - i, self.width() - (10 - i) * 2, self.height() - (10 - i) * 2) 95 | # i_path.addRect(ref) 96 | i_path.addRoundedRect(ref, self.border_width, self.border_width) 97 | color.setAlpha(int(150 - i ** 0.5 * 50)) 98 | pat.setPen(color) 99 | pat.drawPath(i_path) 100 | 101 | # 圆角 102 | pat2 = QtGui.QPainter(self) 103 | pat2.setRenderHint(pat2.Antialiasing) # 抗锯齿 104 | pat2.setBrush(Qt.white) 105 | pat2.setPen(Qt.transparent) 106 | 107 | 108 | class MoveTop(QWidget): 109 | def __init__(self, parent): 110 | super(MoveTop, self).__init__() 111 | self.win = parent 112 | self.InitializeWindow() 113 | self.setStyleSheet(""" 114 | QWidget{ 115 | background-color:rgba(255,0,255,0); 116 | border:none; 117 | margin:0px; 118 | }""") 119 | 120 | def InitializeWindow(self): 121 | self.isPressed = False 122 | top = QLabel() 123 | layout_h = QHBoxLayout() 124 | layout_h.addWidget(top) 125 | self.setLayout(layout_h) 126 | 127 | def mousePressEvent(self, event): # 设置鼠标拖动Label 128 | if event.button() == Qt.LeftButton: # 如果鼠标按钮等于左键 129 | self.isPressed = True 130 | self.startPos = event.globalPos() 131 | return QWidget().mousePressEvent(event) 132 | 133 | def mouseReleaseEvent(self, event): 134 | if event.button() == Qt.LeftButton: # 如果鼠标按钮等于左键 135 | self.isPressed = False 136 | return QWidget().mouseReleaseEvent(event) 137 | 138 | def mouseMoveEvent(self, event): 139 | if self.isPressed: 140 | if self.win.isMaximized: 141 | self.win.showNormal() 142 | 143 | movePos = event.globalPos() - self.startPos 144 | self.startPos = event.globalPos() 145 | self.win.move(self.win.pos() + movePos) 146 | 147 | return QWidget().mouseMoveEvent(event) 148 | 149 | 150 | class RidList(RoundShadow, QWidget): 151 | _signal = QtCore.pyqtSignal(str) 152 | 153 | def __init__(self, parent=None): 154 | super(RidList, self).__init__(parent) 155 | self.rid_dict = {} 156 | self.config_ini_path = '{}/config.ini'.format(os.getcwd()) 157 | self.setup_ui() 158 | 159 | def setup_ui(self): 160 | self.resize(550, 608) 161 | self.setWindowFlags( 162 | QtCore.Qt.Window | QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowMinimizeButtonHint | QtCore.Qt.WindowMaximizeButtonHint) # 隐藏标题栏且可以任务栏最小化 163 | # self.setMinimumSize(QtCore.QSize(550, 600)) # 设置透明窗口最小尺寸 164 | self.setWindowTitle("直播间列表") 165 | self.setWindowIcon(QtGui.QIcon("./image/icon.png")) 166 | 167 | self.label_bottom = QtWidgets.QLabel(self) # 创建背景标签 168 | self.label_bottom.setGeometry(5, 60, 540, 540) 169 | self.label_bottom.setStyleSheet("""QLabel{ 170 | border:none; 171 | border-bottom:1px solid #E9E9E9; 172 | background-color:rgba(255,255,255,1); 173 | border-bottom-left-radius:5px; 174 | border-bottom-right-radius:5px; 175 | } 176 | """) 177 | self.button_close = PushButton("./image/close_icon_2.png", "0,0,0", 0, 200, 0, 178 | '''border:none;background-repeat:no-repeat;background-position:center;margin-right:0px;border-radius:5px;}QPushButton:hover{background-image:url(./image/close_icon_3.png);''') 179 | self.button_close.setObjectName("pushButton_close") 180 | self.button_close.setMinimumSize(QtCore.QSize(39, 39)) 181 | self.button_close.setMaximumSize(QtCore.QSize(39, 39)) 182 | self.button_close.setToolTip("关闭") 183 | 184 | self.label_title = QLabel() 185 | self.label_title.setText("直播间列表") 186 | self.label_title.setFixedSize(100, 35) 187 | self.label_title.setAlignment(Qt.AlignCenter) 188 | self.label_title.setStyleSheet("""QLabel{ 189 | border:none; 190 | border-radius:16px; 191 | font-family:"Microsoft YaHei"; 192 | font-weight:bold; 193 | text-align:center; 194 | color:#474747; 195 | font-size:16px; 196 | background-color:none;; 197 | } 198 | """) 199 | 200 | self.label_top = QLabel(self) 201 | self.label_top.setGeometry(QtCore.QRect(5, 0, 540, 60)) 202 | self.label_top.setStyleSheet(""" 203 | QLabel{ 204 | border-top:1px solid #E9E9E9; 205 | border-bottom:1px solid #E7E7E7; 206 | background-color:rgba(255,255,255,1); 207 | border-top-left-radius:5px; 208 | border-top-right-radius:5px; 209 | } 210 | """) 211 | 212 | self.move_top_left = MoveTop(self) 213 | self.move_top_left.setFixedWidth(210) 214 | self.move_top_left.setObjectName("move_top_title") 215 | self.move_top_right = MoveTop(self) 216 | self.move_top_right.setObjectName("move_top_title") 217 | 218 | self.hbox_top = QHBoxLayout(self.label_top) 219 | self.hbox_top.addWidget(self.move_top_left) 220 | self.hbox_top.addWidget(self.label_title, Qt.AlignHCenter | Qt.AlignVCenter) 221 | self.hbox_top.addWidget(self.move_top_right) 222 | self.hbox_top.addWidget(self.button_close, Qt.AlignRight) 223 | self.hbox_top.setContentsMargins(10, 0, 10, 0) # 内边距 224 | self.hbox_top.setSpacing(0) 225 | 226 | self.label_scroll = QLabel(self.label_bottom) 227 | self.label_scroll.setMinimumSize(540, 500) 228 | self.label_scroll.setStyleSheet("""QLabel{ 229 | 230 | border-bottom-left-radius:5px; 231 | border-bottom-right-radius:5px; 232 | }""") 233 | 234 | self.scroll = QScrollArea() 235 | self.scroll.setWidget(self.label_scroll) 236 | self.scroll.setGeometry(QRect(0, 0, 540, 500)) # 滚动条阈值 237 | self.scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) 238 | self.scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) 239 | self.scroll.setStyleSheet(""" 240 | QScrollArea{ 241 | border-bottom-left-radius:5px; 242 | border-bottom-right-radius:5px; 243 | background-color:#ffffff; 244 | border-image:url("./image/73114636_bg.png"); 245 | padding:0px 0px 5px 0px; 246 | } 247 | """) 248 | 249 | self.vbox_rid = QVBoxLayout(self.label_scroll) 250 | self.vbox_rid.setSpacing(0) 251 | self.vbox_rid.setContentsMargins(0, 0, 0, 0) # 内边距 252 | self.initial_rid() 253 | 254 | self.vbox_widget = QVBoxLayout(self) 255 | self.vbox_widget.addLayout(self.hbox_top) 256 | self.vbox_widget.addWidget(self.scroll) 257 | self.vbox_widget.setContentsMargins(5, 60, 5, 10) # 内边距 258 | self.vbox_widget.setSpacing(0) 259 | self.button_close.clicked.connect(self.closes) 260 | 261 | def label_text(self, text): 262 | self.label_title.setText(text) 263 | self.label_text_animation = QtCore.QVariantAnimation( # 创建一个动画 264 | startValue=0, 265 | endValue=100, 266 | valueChanged=self._update_stylesheet, # 更改值的函数 267 | duration=1500, 268 | ) 269 | self.label_text_animation.start() 270 | 271 | def initial_rid(self): 272 | if os.access(self.config_ini_path, os.F_OK): 273 | self.config = ConfigObj(self.config_ini_path, encoding='UTF8') 274 | self.rid_dict = self.config['rid'] 275 | real_height = 20 276 | if self.rid_dict: 277 | self.rid_dict = ast.literal_eval(self.config['rid']) 278 | else: 279 | self.rid_dict = {} 280 | for i in range(1000): 281 | if i in self.rid_dict.keys(): 282 | self.vbox_rid.addWidget( 283 | self.lineedit_add(i, str(self.rid_dict[i]))) 284 | else: 285 | self.vbox_rid.addWidget(self.lineedit_add(i)) 286 | real_height += 50 287 | self.label_scroll.setMinimumHeight(real_height) 288 | 289 | def kugou_get_room_id(self, url): 290 | headers = { 291 | 'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1', 292 | } 293 | res = requests.get(url, headers, timeout=2).text 294 | rid = re.findall('roomId=(\d+)', res)[0] 295 | return rid 296 | 297 | def get_room_id(self, url): 298 | headers = { 299 | 'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1', 300 | } 301 | url = re.search(r'(https.*)', url).group(1) 302 | response = requests.head(url, headers=headers, timeout=2) 303 | url = response.headers['location'] 304 | room_id = re.search(r'\d{19}', url).group(0) 305 | 306 | headers.update({ 307 | 'cookie': '_tea_utm_cache_1128={%22utm_source%22:%22copy%22%2C%22utm_medium%22:%22android%22%2C%22utm_campaign%22:%22client_share%22}', 308 | 'host': 'webcast.amemv.com', 309 | }) 310 | params = ( 311 | ('type_id', '0'), 312 | ('live_id', '1'), 313 | ('room_id', room_id), 314 | ('app_id', '1128'), 315 | ('X-Bogus', '1'), 316 | ) 317 | 318 | response = requests.get('https://webcast.amemv.com/webcast/room/reflow/info/?', headers=headers, 319 | params=params, timeout=2).json() 320 | if response: 321 | return response['data']['room']['owner']['web_rid'] 322 | 323 | def save_rid(self, index, rid): 324 | if rid: 325 | if 'live.douyin.com' in rid: 326 | rid = re.findall('(\d+)', rid)[0] 327 | elif 'v.douyin.com' in rid: 328 | try: 329 | rid = self.get_room_id(rid) 330 | except: 331 | rid = "" 332 | elif 'live.bilibili.com' in rid: 333 | rid = re.search('\d+', rid).group() 334 | elif 'douyu.com' in rid: 335 | if 'rid=' in rid: 336 | rid = re.findall('rid=(\d+)', rid)[0] 337 | else: 338 | rid = re.search('\d+', rid).group() 339 | elif 'huya.com' in rid: 340 | rid = rid.split("/")[-1] 341 | elif 'yy.com' in rid: 342 | rid = re.findall('/(\d+)/', rid)[0] 343 | elif 'kuwo.cn' in rid: 344 | rid = re.findall('/(\d+)', rid)[0] 345 | elif 'kugou.com' in rid: 346 | if 'channel' in rid: 347 | try: 348 | rid = self.kugou_get_room_id(rid) 349 | except: 350 | rid = "" 351 | else: 352 | rid = re.findall('/(\d+)', rid)[0] 353 | self.rid_dict.update({index: rid}) 354 | 355 | 356 | else: 357 | self.rid_dict.pop(index) 358 | self.config['rid'] = str(self.rid_dict) 359 | self.config.write() 360 | return rid 361 | 362 | def lineedit_add(self, index, rid_value=None): 363 | lineedit_text = QLineEdit() 364 | lineedit_text.setFixedHeight(50) 365 | lineedit_text.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) 366 | lineedit_text.setStyleSheet("""QLineEdit{ 367 | font-family:"Microsoft YaHei"; 368 | border-radius:0px; 369 | border-bottom:1px solid #E5E5E5; 370 | font-size:25px; 371 | color:#4C4B50; 372 | background-color:rgba(0,0,0,0); 373 | } 374 | QLineEdit:hover{ 375 | border-bottom:1px solid #C1C1C1; 376 | } 377 | QLineEdit:focus{ 378 | border-bottom:2px solid #12B7F5 379 | }""") 380 | 381 | def set_text(): 382 | try: 383 | rid = self.save_rid(index, lineedit_text.text()) 384 | lineedit_text.setText(rid) 385 | except: 386 | lineedit_text.setText("") 387 | 388 | # lineedit_text.textEdited[str].connect(lambda: self.save_rid(index, lineedit_text.text())) 389 | lineedit_text.textEdited[str].connect(set_text) 390 | if rid_value: 391 | lineedit_text.setText(rid_value) 392 | return lineedit_text 393 | 394 | def closes(self): 395 | self.close() 396 | 397 | 398 | if __name__ == '__main__': 399 | app = QApplication(sys.argv) 400 | real_rid = RidList() 401 | real_rid.show() 402 | sys.exit(app.exec_()) 403 | -------------------------------------------------------------------------------- /RealTool.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time:2022/9/22 15:40 3 | # @Author: 须尽欢 4 | # @File:RealTool.py 5 | # Software:PyCharm 6 | # Created by: PyQt5 UI code generator 5.14.2 7 | # 直播源项目地址@https://github.com/wbt5/real-url 8 | # m3u8录制工具地址@https://github.com/nilaoda/N_m3u8DL-RE 9 | 10 | 11 | from PyQt5 import QtGui, QtWidgets, QtCore 12 | from PyQt5.QtWidgets import QApplication, QInputDialog, QWidget, QVBoxLayout, QHBoxLayout, QLabel, \ 13 | QGraphicsDropShadowEffect, QPushButton, QLineEdit, QMessageBox 14 | from PyQt5.Qt import QPropertyAnimation, pyqtSignal, QTranslator, QColorDialog, QFileDialog, QRegExp, QRegExpValidator 15 | from PyQt5.QtGui import QIcon 16 | from PyQt5.QtCore import Qt, QEvent, QThread 17 | import sys 18 | import time 19 | import os 20 | import subprocess 21 | from configobj import ConfigObj 22 | import config_ini 23 | import re 24 | import requests 25 | import ast 26 | from multiprocessing.pool import ThreadPool 27 | 28 | from real.douyu import DouYu 29 | from real.huya import HuYa 30 | from real.bili import BiliBili 31 | from real.douyin import DouYin 32 | from real.yy import YY 33 | from real.kuwo import KuWo 34 | from real.kugou import KuGou 35 | from real.yizhibo import YiZhiBo 36 | from RealListWindow import RealList 37 | from RealRid import RidList 38 | import urllib3 39 | from selenium import webdriver 40 | from selenium.webdriver.chrome.options import Options 41 | urllib3.disable_warnings() 42 | 43 | class ThreadGet(QThread): 44 | _signal = pyqtSignal(dict, str) 45 | 46 | def __init__(self, real_list): 47 | super(ThreadGet, self).__init__() 48 | self.real_list = real_list 49 | self.config = ConfigObj("config.ini", encoding='UTF8') 50 | 51 | def run(self): 52 | real_dict = {} 53 | thread_list = [] 54 | pool = ThreadPool(processes=10) 55 | for rid in self.real_list: 56 | for real_name_dict in self.config['real']: 57 | try: 58 | if real_name_dict == "douyu" and self.config['real'][real_name_dict] == "1": 59 | real_class = DouYu(rid) 60 | thread_list.append(pool.apply_async(real_class.get_real_url)) 61 | 62 | if real_name_dict == "huya" and self.config['real'][real_name_dict] == "1": 63 | real_class = HuYa(rid) 64 | thread_list.append(pool.apply_async(real_class.get_real_url)) 65 | 66 | if real_name_dict == "bili" and self.config['real'][real_name_dict] == "1": 67 | real_class = BiliBili(rid) 68 | thread_list.append(pool.apply_async(real_class.get_real_url)) 69 | 70 | if real_name_dict == "douyin" and self.config['real'][real_name_dict] == "1": 71 | real_class = DouYin(rid) 72 | thread_list.append(pool.apply_async(real_class.get_real_url)) 73 | 74 | if real_name_dict == "yy" and self.config['real'][real_name_dict] == "1": 75 | real_class = YY(rid) 76 | thread_list.append(pool.apply_async(real_class.get_real_url)) 77 | 78 | if real_name_dict == "kuwo" and self.config['real'][real_name_dict] == "1": 79 | real_class = KuWo(rid) 80 | thread_list.append(pool.apply_async(real_class.get_real_url)) 81 | 82 | if real_name_dict == "kugou" and self.config['real'][real_name_dict] == "1": 83 | real_class = KuGou(rid) 84 | thread_list.append(pool.apply_async(real_class.get_real_url)) 85 | 86 | if real_name_dict == "yizhibo" and self.config['real'][real_name_dict] == "1": 87 | real_class = YiZhiBo(rid) 88 | thread_list.append(pool.apply_async(real_class.get_real_url)) 89 | except Exception as e: 90 | print("e:%s" % e) 91 | for thread in thread_list: 92 | try: 93 | return_dict = thread.get() 94 | count = 0 95 | if return_dict: 96 | if real_dict: 97 | for i in range(len(real_dict)): 98 | while list(return_dict.keys())[0] == list(real_dict.keys())[i]: 99 | count += 1 100 | if f'{list(return_dict.keys())[0].split("_")[0]}_{count}' != list(real_dict.keys())[i]: 101 | return_dict.update( 102 | {f'{list(return_dict.keys())[0].split("_")[0]}_{count}': return_dict.pop( 103 | list(return_dict.keys())[0])}) 104 | break 105 | 106 | real_dict.update(return_dict) 107 | except Exception as err: 108 | print("err:%s" % err) 109 | if real_dict: 110 | print(real_dict) 111 | self._signal.emit(real_dict, 'true') 112 | else: 113 | self._signal.emit(real_dict, 'false') 114 | pool.close() 115 | 116 | 117 | class ButtonGet(QtWidgets.QPushButton): 118 | def __init__(self, parent=None): 119 | super().__init__(parent) 120 | 121 | self._animation = QtCore.QVariantAnimation( # 创建一个动画 122 | startValue=0, 123 | endValue=100, 124 | valueChanged=self._on_value_changed, # 更改值的函数 125 | duration=400, 126 | ) 127 | self._update_stylesheet(hover_g_start=200, hover_g_stop=180) 128 | 129 | def _on_value_changed(self, a): # 更新 改变后的值 130 | hover_g_start = int(200 + (a / 5)) 131 | hover_g_stop = int(180 + (a / 5)) 132 | self._update_stylesheet(hover_g_start=hover_g_start, hover_g_stop=hover_g_stop) # 更新并调用样式 133 | 134 | def _update_stylesheet(self, hover_g_start=None, hover_g_stop=None): # 更新样式函数 135 | self.setStyleSheet(""" 136 | QPushButton{ 137 | border-solid: 3px; 138 | color:#ffffff; 139 | background-color:qlineargradient( 140 | spread:pad, x1:0, y1:0, x2:0, y2:1, 141 | stop:0 rgba(11,%s,255, 1), 142 | stop:1 rgba(0,%s,255, 1)); 143 | } 144 | QPushButton:pressed{ 145 | background-color:qlineargradient( 146 | spread:pad, x1:0, y1:0, x2:0, y2:1, 147 | stop:0 rgba(11,190,255, 1), 148 | stop:1 rgba(0,185,255, 1)); 149 | 150 | }""" % (hover_g_start, hover_g_stop)) 151 | 152 | def enterEvent(self, event): # 进入 153 | self._animation.setDirection(QtCore.QAbstractAnimation.Forward) # 正 154 | self._animation.start() 155 | super().enterEvent(event) 156 | 157 | def leaveEvent(self, event): # 离开 158 | self._animation.setDirection(QtCore.QAbstractAnimation.Backward) # 反 159 | self._animation.start() 160 | super().leaveEvent(event) 161 | 162 | 163 | class PushButton(QtWidgets.QPushButton): 164 | def __init__(self, background_img=None, background_color_rgb=None, background_color_rgba=None, duration=None, 165 | button_bg=None, pushbutton_stylesheet=None, parent=None): 166 | """ 167 | :param background_img:图片地址 168 | :param background_color_rgb:红绿蓝颜色 169 | :param background_color_rgba:透明度 0~1 170 | :param duration:持续时间 171 | :param button_bg:默认背景透明度0~1 172 | :param pushbutton_stylesheet:其余样式 173 | :param parent: 174 | """ 175 | super().__init__(parent) 176 | self.background_img = background_img 177 | self.background_color_rgb = background_color_rgb 178 | self.background_color_rgba = background_color_rgba 179 | self.button_bg = button_bg 180 | self.pushbutton_stylesheet = pushbutton_stylesheet 181 | 182 | self._animation = QtCore.QVariantAnimation( # 创建一个动画 183 | startValue=0, 184 | endValue=100, 185 | valueChanged=self._on_value_changed, # 更改值的函数 186 | duration=duration, 187 | ) 188 | self._update_stylesheet(self.button_bg) 189 | 190 | def _on_value_changed(self, a): # 更新 改变后的值 191 | a = a / 100 * self.background_color_rgba + self.button_bg 192 | self._update_stylesheet(a) # 更新并调用样式 193 | 194 | def _update_stylesheet(self, a): # 更新样式函数 195 | self.setStyleSheet("""QPushButton{ 196 | background-image: url(%s); 197 | background-color:rgba(%s,%s); 198 | %s} 199 | """ % (self.background_img, self.background_color_rgb, a, self.pushbutton_stylesheet)) 200 | 201 | def enterEvent(self, event): # 进入 202 | self._animation.setDirection(QtCore.QAbstractAnimation.Forward) # 正 203 | self._animation.start() 204 | super().enterEvent(event) 205 | 206 | def leaveEvent(self, event): # 离开 207 | self._animation.setDirection(QtCore.QAbstractAnimation.Backward) # 反 208 | self._animation.start() 209 | super().leaveEvent(event) 210 | 211 | 212 | class ToolButton(QtWidgets.QToolButton): 213 | def __init__(self, background_img=None, background_color_rgb=None, background_color_rgba=None, duration=None, 214 | button_bg=None, pushbutton_stylesheet=None, parent=None): 215 | """ 216 | :param background_img:图片地址 217 | :param background_color_rgb:红绿蓝颜色 218 | :param background_color_rgba:透明度 0~1 219 | :param duration:持续时间 220 | :param button_bg:默认背景透明度0~1 221 | :param pushbutton_stylesheet:其余样式 222 | :param parent: 223 | """ 224 | super().__init__(parent) 225 | self.background_img = background_img 226 | self.background_color_rgb = background_color_rgb 227 | self.background_color_rgba = background_color_rgba 228 | self.button_bg = button_bg 229 | self.pushbutton_stylesheet = pushbutton_stylesheet 230 | 231 | self._animation = QtCore.QVariantAnimation( # 创建一个动画 232 | startValue=0, 233 | endValue=100, 234 | valueChanged=self._on_value_changed, # 更改值的函数 235 | duration=duration, 236 | ) 237 | self._update_stylesheet(self.button_bg) 238 | 239 | def _on_value_changed(self, a): # 更新 改变后的值 240 | a = a / 100 * self.background_color_rgba + self.button_bg 241 | self._update_stylesheet(a) # 更新并调用样式 242 | 243 | def _update_stylesheet(self, a): # 更新样式函数 244 | self.setStyleSheet("""QToolButton{ 245 | background-image: url(%s); 246 | background-color:rgba(%s,%s); 247 | } 248 | QToolButton::menu-indicator{ 249 | image:none; 250 | 251 | %s} 252 | """ % (self.background_img, self.background_color_rgb, a, self.pushbutton_stylesheet)) 253 | 254 | def enterEvent(self, event): # 进入 255 | self._animation.setDirection(QtCore.QAbstractAnimation.Forward) # 正 256 | self._animation.start() 257 | super().enterEvent(event) 258 | 259 | def leaveEvent(self, event): # 离开 260 | self._animation.setDirection(QtCore.QAbstractAnimation.Backward) # 反 261 | self._animation.start() 262 | super().leaveEvent(event) 263 | 264 | 265 | class MainUi(QWidget): 266 | def __init__(self): 267 | super(MainUi, self).__init__() 268 | self.setup_ui() 269 | self.real_dict = {} 270 | self.real_id = '' 271 | self.add_shadow() # 阴影边框 272 | 273 | def setup_ui(self): 274 | 275 | self.config_ini() 276 | self.setWindowFlags( 277 | QtCore.Qt.Window | QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowMinimizeButtonHint | QtCore.Qt.WindowMaximizeButtonHint) # 隐藏标题栏且可以任务栏最小化 278 | self.setFixedSize(self.width_, self.height_) 279 | self.setStyleSheet("""QWidget{ 280 | border-radius:3px; 281 | border:none; 282 | }""") 283 | self.setWindowIcon(QIcon("./image/icon.png")) 284 | self.setWindowTitle("直播源获取") 285 | self.setAttribute(QtCore.Qt.WA_TranslucentBackground) # 设置窗口背景透明 286 | self.label_background = QLabel(self) # 创建背景标签 287 | self.label_background.setGeometry(QtCore.QRect(5, 0, self.width_ - 10, self.height_ - 10)) 288 | self.label_background.setStyleSheet("""QLabel{ 289 | border:none; 290 | background-color:rgba(135,249,255, 1); 291 | }""") 292 | 293 | self.label_top_background = QLabel() 294 | self.label_top_background.setFixedSize(self.width_ - 10, int(self.height_ / 2.7)) 295 | self.label_top_background.setStyleSheet("""QLabel{ 296 | border:none; 297 | border-bottom-left-radius:0px; 298 | border-bottom-right-radius:0px; 299 | }""") 300 | 301 | self.label_top_background_image = QLabel(self.label_top_background) 302 | self.label_top_background_image.setFixedSize(self.width_ - 10, int(self.height_ / 2.7)) 303 | self.label_top_background_image.setStyleSheet("""QLabel{ 304 | border:none; 305 | border-bottom-left-radius:0px; 306 | border-bottom-right-radius:0px; 307 | border-image:url('%s'); 308 | }""" % self.config[ 309 | 'background']) 310 | opacity_effect = QtWidgets.QGraphicsOpacityEffect() 311 | opacity_effect.setOpacity(0.85) 312 | self.label_top_background_image.setGraphicsEffect(opacity_effect) 313 | self.label_top_background_image.setAutoFillBackground(True) 314 | 315 | self.label_bottom_background = QLabel() 316 | self.label_bottom_background.setFixedSize(self.label_top_background.width(), 317 | self.height_ - self.label_top_background.height() - 10) 318 | self.label_bottom_background.setStyleSheet("""QLabel{ 319 | background-color:#ffffff; 320 | border-top-left-radius:0px; 321 | border-top-right-radius:0px; 322 | }""") 323 | 324 | self.label_icon = QLabel(self.label_top_background) 325 | self.label_icon.setFixedSize(self.label_top_background.width(), 32) 326 | self.label_icon.setStyleSheet("""QLabel{ 327 | border:none; 328 | border-image:none; 329 | background-color:rgba(255, 255, 255, 0); 330 | }""") 331 | 332 | self.layout_icon = QtWidgets.QHBoxLayout(self.label_icon) 333 | 334 | self.pushbutton_setting = ToolButton(background_img="./image/setting_icon.png", 335 | background_color_rgb="255,255,255", background_color_rgba=0.2, 336 | duration=200, button_bg=0, 337 | pushbutton_stylesheet="""border:none;background-repeat:no-repeat;width:35px;background-position:center;}QToolButton:pressed{background-color:rgba(255,255,255,0.4);}""") # 设置按钮样式 338 | self.pushbutton_setting.setFixedSize(32, 32) 339 | self.pushbutton_setting.setToolTip("设置") 340 | 341 | self.pushbutton_colse = PushButton(background_img="./image/close_icon.png", background_color_rgb="255,84,57", 342 | background_color_rgba=1, duration=200, button_bg=0, 343 | pushbutton_stylesheet="""border:none;background-repeat:no-repeat;width:32px;background-position:center;}QPushButton:pressed{background-color:rgba(224,74,50,1);}""") # 关闭按钮样式 344 | self.pushbutton_colse.setFixedSize(32, 32) 345 | self.pushbutton_colse.clicked.connect(self.close_) 346 | self.pushbutton_colse.setToolTip('关闭 Ese') 347 | 348 | self.pushbutton_min = PushButton(background_img="./image/min_icon.png", background_color_rgb="255,255,255", 349 | background_color_rgba=0.2, duration=200, button_bg=0, 350 | pushbutton_stylesheet="""border:none;background-repeat:no-repeat;width:35px;background-position:center;}QPushButton:pressed{background-color:rgba(255,255,255,0.4);}""") # 最小化按钮样式 351 | self.pushbutton_min.setFixedSize(32, 32) 352 | self.pushbutton_min.clicked.connect(self.min_) 353 | self.pushbutton_min.setToolTip('最小化') 354 | 355 | self.pushbutton_size = PushButton(background_img="./image/size_icon.png", background_color_rgb="255,255,255", 356 | background_color_rgba=0.2, duration=200, button_bg=0, 357 | pushbutton_stylesheet="""border:none;background-repeat:no-repeat;width:35px;background-position:center;}QPushButton:pressed{background-color:rgba(255,255,255,0.4);}""") # 切换窗口按钮样式 358 | self.pushbutton_size.setFixedSize(32, 32) 359 | self.pushbutton_size.clicked.connect(self.size_) 360 | self.pushbutton_size.setToolTip('切换窗口大小') 361 | 362 | self.lineedit_input = QLineEdit() 363 | self.lineedit_input.setFixedSize(int(self.width_ - 194), 80) 364 | self.lineedit_input.setAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignBottom) 365 | self.lineedit_input.setStyleSheet(""" 366 | QLineEdit{ 367 | color:%s; 368 | font-family:"Microsoft YaHei"; 369 | font-size:%spx; 370 | border-radius:0px; 371 | border-bottom:1px solid #E5E5E5; 372 | } 373 | QLineEdit:hover{ 374 | border-bottom:1px solid #C1C1C1; 375 | } 376 | QLineEdit:focus{ 377 | border-bottom:1px solid #12B7F5 378 | }""" % ( 379 | self.config['font']['color'], self.config['font']['size'])) 380 | rx = QRegExp("[\S]*") 381 | validator = QRegExpValidator(self) 382 | validator.setRegExp(rx) 383 | self.lineedit_input.setValidator(validator) # 设置屏蔽空格 384 | 385 | self.pushbutton_get = ButtonGet() 386 | self.pushbutton_get.setFixedSize(int(self.width_ - 194), 38) 387 | self.pushbutton_get.setText("获取") 388 | button_font = QtGui.QFont() 389 | button_font.setFamily("微软雅黑") 390 | button_font.setPointSize(11) 391 | self.pushbutton_get.setFont(button_font) 392 | 393 | self.hbox_lineedit = QHBoxLayout() 394 | self.hbox_lineedit.addStretch() 395 | self.hbox_lineedit.addWidget(self.lineedit_input) 396 | self.hbox_lineedit.addStretch() 397 | 398 | self.hbox_button = QHBoxLayout() 399 | self.hbox_button.addStretch() 400 | self.hbox_button.addWidget(self.pushbutton_get) 401 | self.hbox_button.addStretch() 402 | 403 | self.vbox_button = QVBoxLayout(self.label_bottom_background) 404 | self.vbox_button.addStretch() 405 | self.vbox_button.addLayout(self.hbox_lineedit) 406 | self.vbox_button.addLayout(self.hbox_button) 407 | self.vbox_button.addStretch() 408 | self.vbox_button.setSpacing(10) 409 | self.vbox_button.setContentsMargins(0, 0, 0, 0) 410 | 411 | self.layout_icon.addStretch() 412 | self.layout_icon.addWidget(self.pushbutton_setting, 0, Qt.AlignRight) 413 | self.layout_icon.addWidget(self.pushbutton_size, 0, Qt.AlignRight) 414 | self.layout_icon.addWidget(self.pushbutton_min, 0, Qt.AlignRight) 415 | self.layout_icon.addWidget(self.pushbutton_colse, 0, Qt.AlignRight) 416 | 417 | self.layout_icon.setSpacing(0) # 外间距 418 | self.layout_icon.setContentsMargins(0, 0, 0, 0) # 内间距 419 | 420 | self.vbox_background = QVBoxLayout(self.label_background) 421 | self.vbox_background.addWidget(self.label_top_background) 422 | self.vbox_background.addWidget(self.label_bottom_background) 423 | self.vbox_background.setSpacing(0) 424 | self.vbox_background.setContentsMargins(0, 0, 0, 0) 425 | 426 | self.pushbutton_logo = QPushButton(self.label_background) 427 | self.pushbutton_logo.setToolTip("") 428 | self.pushbutton_logo.setGeometry(int(self.width_ / 2 - 47 - 5), int(self.height_ / 2.7 - 47), 94, 94) 429 | self.pushbutton_logo.setStyleSheet("""QPushButton{ 430 | border-image: url('./image/logo.png'); 431 | }""") 432 | 433 | self.menu_tool = QtWidgets.QMenu(self.pushbutton_setting) 434 | 435 | self.menu_dir = QtWidgets.QMenu("打开目录", self.menu_tool) 436 | self.open_dir = QtWidgets.QAction("软件目录", self.menu_dir) 437 | self.open_path = QtWidgets.QAction("下载目录", self.menu_dir) 438 | self.menu_dir.addAction(self.open_dir) 439 | self.menu_dir.addAction(self.open_path) 440 | 441 | self.menu_real = QtWidgets.QMenu("直播平台", self.menu_tool) 442 | self.menu_real.mouseReleaseEvent = self.mouse_release_event_real_ 443 | self.real_douyu = QtWidgets.QAction("斗鱼直播", self.menu_real) 444 | self.real_huya = QtWidgets.QAction("虎牙直播", self.menu_real) 445 | self.real_bili = QtWidgets.QAction("哔哩直播", self.menu_real) 446 | self.real_douyin = QtWidgets.QAction("抖音直播", self.menu_real) 447 | self.real_yy = QtWidgets.QAction("歪歪直播", self.menu_real) 448 | self.real_kuwo = QtWidgets.QAction("酷我直播", self.menu_real) 449 | self.real_kugou = QtWidgets.QAction("酷狗直播", self.menu_real) 450 | self.real_yizhibo = QtWidgets.QAction("一直播", self.menu_real) 451 | 452 | self.menu_real.addAction(self.real_douyu) 453 | self.menu_real.addAction(self.real_huya) 454 | self.menu_real.addAction(self.real_bili) 455 | self.menu_real.addAction(self.real_douyin) 456 | self.menu_real.addAction(self.real_yy) 457 | self.menu_real.addAction(self.real_kuwo) 458 | self.menu_real.addAction(self.real_kugou) 459 | self.menu_real.addAction(self.real_yizhibo) 460 | self.real_douyu.setCheckable(True) 461 | self.real_huya.setCheckable(True) 462 | self.real_bili.setCheckable(True) 463 | self.real_douyin.setCheckable(True) 464 | self.real_yy.setCheckable(True) 465 | self.real_kuwo.setCheckable(True) 466 | self.real_kugou.setCheckable(True) 467 | self.real_yizhibo.setCheckable(True) 468 | 469 | self.menu_about = QtWidgets.QMenu("关于软件", self.menu_tool) 470 | self.about_down = QtWidgets.QAction("下载地址", self.menu_about) 471 | self.about_project = QtWidgets.QAction("开源项目", self.menu_about) 472 | self.about_explain = QtWidgets.QAction("软件说明", self.menu_about) 473 | self.menu_about.addAction(self.about_down) 474 | self.menu_about.addAction(self.about_project) 475 | self.menu_about.addAction(self.about_explain) 476 | self.about_down.triggered.connect(self.url_down_) 477 | self.about_project.triggered.connect(self.url_project_) 478 | self.about_explain.triggered.connect(self.explain_) 479 | 480 | self.menu_setting = QtWidgets.QMenu("其它设置", self.menu_tool) 481 | self.menu_play = QtWidgets.QMenu("播放设置", self.menu_setting) 482 | self.menu_play.mouseReleaseEvent = self.mouse_play_event_real_ 483 | self.play_pot = QtWidgets.QAction("PotPlayer", self.menu_play) 484 | self.play_mpv = QtWidgets.QAction("MpvPlayer", self.menu_play) 485 | self.play_pot.setCheckable(True) 486 | self.play_mpv.setCheckable(True) 487 | 488 | self.menu_play.addAction(self.play_pot) 489 | self.menu_play.addAction(self.play_mpv) 490 | 491 | self.menu_setting.addMenu(self.menu_play) 492 | self.setting_rid = QtWidgets.QAction("批量获取", self.menu_setting) 493 | self.setting_background = QtWidgets.QAction("背景设置", self.menu_setting) 494 | self.setting_font_size = QtWidgets.QAction("字体大小", self.menu_setting) 495 | self.setting_font_color = QtWidgets.QAction("字体颜色", self.menu_setting) 496 | self.setting_reset = QtWidgets.QAction("重置设置", self.menu_setting) 497 | self.menu_setting.addAction(self.setting_rid) 498 | self.menu_setting.addAction(self.setting_background) 499 | self.menu_setting.addAction(self.setting_font_size) 500 | self.menu_setting.addAction(self.setting_font_color) 501 | self.menu_setting.addAction(self.setting_reset) 502 | 503 | self.menu_tool.addMenu(self.menu_dir) 504 | self.menu_tool.addMenu(self.menu_real) 505 | self.menu_tool.addMenu(self.menu_setting) 506 | self.menu_tool.addMenu(self.menu_about) 507 | 508 | self.pushbutton_setting.setMenu(self.menu_tool) 509 | self.pushbutton_setting.setPopupMode(QtWidgets.QToolButton.InstantPopup) 510 | 511 | self.initial_live() 512 | self.open_dir.triggered.connect(self.open_dir_) 513 | self.open_path.triggered.connect(self.open_path_) 514 | self.setting_reset.triggered.connect(self.reset_) 515 | self.setting_font_color.triggered.connect(self.font_color_) 516 | self.setting_rid.triggered.connect(self.real_rid_) 517 | self.setting_font_size.triggered.connect(self.font_size_) 518 | self.setting_background.triggered.connect(self.background_image_) 519 | self.pushbutton_get.clicked.connect(self.thread_get) 520 | self.real_douyu.triggered.connect(self.real_live_) 521 | self.real_huya.triggered.connect(self.real_live_) 522 | self.real_bili.triggered.connect(self.real_live_) 523 | self.real_douyin.triggered.connect(self.real_live_) 524 | self.real_yy.triggered.connect(self.real_live_) 525 | self.real_kuwo.triggered.connect(self.real_live_) 526 | self.real_kugou.triggered.connect(self.real_live_) 527 | self.real_yizhibo.triggered.connect(self.real_live_) 528 | self.pushbutton_logo.clicked.connect(lambda: self.real_window()) 529 | self.play_pot.triggered.connect(lambda: self.player_('pot')) 530 | self.play_mpv.triggered.connect(lambda: self.player_('mpv')) 531 | 532 | def initial_live(self): 533 | self.config = ConfigObj("config.ini", encoding="UTF8") 534 | for real_ in self.config['real']: 535 | if real_ == 'douyu': 536 | if self.config['real'][real_] == "1": 537 | self.real_douyu.setChecked(True) 538 | if real_ == 'huya': 539 | if self.config['real'][real_] == "1": 540 | self.real_huya.setChecked(True) 541 | if real_ == 'bili': 542 | if self.config['real'][real_] == "1": 543 | self.real_bili.setChecked(True) 544 | if real_ == 'douyin': 545 | if self.config['real'][real_] == "1": 546 | self.real_douyin.setChecked(True) 547 | if real_ == 'yy': 548 | if self.config['real'][real_] == "1": 549 | self.real_yy.setChecked(True) 550 | if real_ == 'kuwo': 551 | if self.config['real'][real_] == "1": 552 | self.real_kuwo.setChecked(True) 553 | if real_ == 'kugou': 554 | if self.config['real'][real_] == "1": 555 | self.real_kugou.setChecked(True) 556 | if real_ == 'yizhibo': 557 | if self.config['real'][real_] == "1": 558 | self.real_yizhibo.setChecked(True) 559 | if self.config['player'] == 'pot': 560 | self.play_pot.setChecked(True) 561 | elif self.config['player'] == 'mpv': 562 | self.play_mpv.setChecked(True) 563 | 564 | def player_(self, name): 565 | self.play_pot.setChecked(False) 566 | self.play_mpv.setChecked(False) 567 | self.config = ConfigObj("config.ini", encoding="UTF8") 568 | if name == 'pot': 569 | self.play_pot.setChecked(True) 570 | self.config['player'] = 'pot' 571 | elif name == 'mpv': 572 | file_, filetype = QFileDialog.getOpenFileName(self, 573 | "选取文件", 574 | os.getcwd(), 575 | "All Files(mpv*.exe)") 576 | if file_: 577 | self.config['mpv'] = file_ 578 | self.play_mpv.setChecked(True) 579 | self.config['player'] = 'mpv' 580 | else: 581 | self.play_pot.setChecked(True) 582 | self.config['player'] = 'pot' 583 | self.config.write() 584 | 585 | def real_rid_(self): 586 | self.real_rid_list = RidList() 587 | self.real_rid_list.setWindowModality(Qt.ApplicationModal) # 禁止非当前窗口操作 588 | self.real_rid_list.show() 589 | 590 | def url_project_(self): 591 | url = 'https://github.com/s282730788/RealTool' 592 | QtGui.QDesktopServices.openUrl(QtCore.QUrl(url)) 593 | 594 | def explain_(self): 595 | 596 | QMessageBox.information(self, 597 | "关于RealTool", 598 | "本软件由吾爱岚瑟首发,现已开源\n仅作为个人学习使用,不得用于任何商业用途!", 599 | QMessageBox.Ok) 600 | 601 | def url_down_(self): 602 | url = 'https://wwjd.lanzout.com/b00w74arg' 603 | QtGui.QDesktopServices.openUrl(QtCore.QUrl(url)) 604 | self.lineedit_input.setText("密码1234") 605 | 606 | def real_window(self): 607 | if self.real_dict: 608 | self.real_list = RealList(self.real_dict) 609 | self.real_list.show() 610 | 611 | def douyin_get_room_id(self, url): 612 | try: 613 | print(url) 614 | chrome_options = Options() 615 | chrome_options.add_argument('--headless') # 无头模式 616 | driver = webdriver.Chrome(options=chrome_options) 617 | driver.implicitly_wait(10) 618 | driver.get(url) 619 | err = 0 620 | while True: 621 | if 'live.douyin' in driver.current_url: 622 | room_id = re.findall("\d+",driver.current_url)[0] 623 | print(room_id) 624 | return room_id 625 | time.sleep(1) 626 | err += 1 627 | if err >= 10: 628 | break 629 | except Exception as err: 630 | print(err) 631 | # headers = { 632 | # 'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1', 633 | # } 634 | # url = re.search(r'(https.*)', url).group(1) 635 | # print(f"111:{url}") 636 | # 637 | # response = requests.head(url, headers=headers, timeout=2, verify=False) 638 | # print(response.status_code) 639 | # 640 | # url = response.headers['location'] 641 | # print(f"222:{url}") 642 | # room_id = re.search(r'\d{19}', url).group(0) 643 | # print(f"333:{room_id}") 644 | # headers.update({ 645 | # 'cookie': '_tea_utm_cache_1128={%22utm_source%22:%22copy%22%2C%22utm_medium%22:%22android%22%2C%22utm_campaign%22:%22client_share%22}', 646 | # 'host': 'webcast.amemv.com', 647 | # }) 648 | # params = ( 649 | # ('type_id', '0'), 650 | # ('live_id', '1'), 651 | # ('room_id', room_id), 652 | # ('app_id', '1128'), 653 | # ('X-Bogus', '1'), 654 | # ) 655 | # 656 | # response = requests.get('https://webcast.amemv.com/webcast/room/reflow/info/?', headers=headers, 657 | # params=params, timeout=2) 658 | # print(response.text) 659 | # print(f"444:{response.json()}") 660 | # if response.json(): 661 | # return response.json()['data']['room']['owner']['web_rid'] 662 | 663 | def kugou_get_room_id(self, url): 664 | headers = { 665 | 'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1', 666 | } 667 | res = requests.get(url, headers, timeout=2).text 668 | rid = re.findall('roomId=(\d+)', res)[0] 669 | return rid 670 | 671 | def thread_get(self): 672 | self.config = ConfigObj("config.ini", encoding="UTF8") 673 | rid = self.lineedit_input.text() 674 | rid_list = [] # 存放RID列表 675 | rid_list_del = [] # 去重 676 | if rid: 677 | if 'live.douyin.com' in rid: 678 | try: 679 | rid = re.findall('(\d+)', rid)[0] 680 | except: 681 | pass 682 | elif 'v.douyin.com' in rid: 683 | try: 684 | rid = self.douyin_get_room_id(rid) 685 | except Exception as err: 686 | print(f"douyin_err:{err}") 687 | rid = "" 688 | elif 'live.bilibili.com' in rid: 689 | rid = re.search('\d+', rid).group() 690 | elif 'douyu.com' in rid: 691 | if 'rid=' in rid: 692 | rid = re.findall('rid=(\d+)', rid)[0] 693 | else: 694 | rid = re.search('\d+', rid).group() 695 | elif 'huya.com' in rid: 696 | rid = rid.split("/")[-1] 697 | elif 'yy.com' in rid: 698 | rid = re.findall('/(\d+)', rid)[0] 699 | elif 'kuwo.cn' in rid: 700 | rid = re.findall('/(\d+)', rid)[0] 701 | elif 'kugou.com' in rid: 702 | if 'channel' in rid: 703 | try: 704 | rid = self.kugou_get_room_id(rid) 705 | except: 706 | rid = "" 707 | else: 708 | rid = re.findall('/(\d+)', rid)[0] 709 | elif 'yizhibo' in rid: 710 | rid = re.findall('yizhibo.com.*/(.*?).html', rid)[0] 711 | print(rid) 712 | rid_list.append(rid) 713 | self.lineedit_input.setText(rid) 714 | self.thread = ThreadGet(rid_list) 715 | self.thread._signal.connect(self.text) 716 | self.thread.start() 717 | self.pushbutton_logo.setStyleSheet("""QPushButton{ 718 | border-image: url('./image/logo_get.png'); 719 | }""") 720 | else: 721 | rid_dict = self.config['rid'] 722 | if rid_dict: 723 | rid_dict = ast.literal_eval(self.config['rid']) 724 | for i in rid_dict: 725 | rid_list.append(rid_dict[i]) 726 | for j in rid_list: 727 | if j not in rid_list_del: 728 | rid_list_del.append(j) 729 | rid_list = rid_list_del 730 | self.thread = ThreadGet(rid_list) 731 | self.thread._signal.connect(self.text) 732 | self.thread.start() 733 | self.pushbutton_logo.setStyleSheet("""QPushButton{ 734 | border-image: url('./image/logo_get.png'); 735 | }""") 736 | 737 | def text(self, real_dict, text): 738 | if text == 'true': 739 | self.real_dict = real_dict 740 | self.real_window() 741 | elif text == 'false': 742 | self.lineedit_input.setText("直播源获取失败") 743 | self.pushbutton_logo.setStyleSheet("""QPushButton{ 744 | border-image: url('./image/logo.png'); 745 | }""") 746 | QApplication.processEvents() 747 | 748 | def real_live_(self): 749 | self.config = ConfigObj("config.ini", encoding="UTF8") 750 | self.config['real']['douyu'] = "0" 751 | self.config['real']['huya'] = "0" 752 | self.config['real']['bili'] = "0" 753 | self.config['real']['douyin'] = "0" 754 | self.config['real']['yy'] = "0" 755 | self.config['real']['kuwo'] = "0" 756 | self.config['real']['kugou'] = "0" 757 | if self.real_douyu.isChecked(): 758 | self.config['real']['douyu'] = "1" 759 | if self.real_huya.isChecked(): 760 | self.config['real']['huya'] = "1" 761 | if self.real_bili.isChecked(): 762 | self.config['real']['bili'] = "1" 763 | if self.real_douyin.isChecked(): 764 | self.config['real']['douyin'] = "1" 765 | if self.real_yy.isChecked(): 766 | self.config['real']['yy'] = "1" 767 | if self.real_kuwo.isChecked(): 768 | self.config['real']['kuwo'] = "1" 769 | if self.real_kugou.isChecked(): 770 | self.config['real']['kugou'] = "1" 771 | if self.real_yizhibo.isChecked(): 772 | self.config['real']['yizhibo'] = "1" 773 | 774 | self.config.write() 775 | 776 | def background_image_(self): 777 | getcwd_file = '{}/image/'.format(os.getcwd()) 778 | background_image, filetype = QFileDialog.getOpenFileName(self, 779 | "选取文件", 780 | getcwd_file, 781 | "img (*jpg;*png;*bmp)") # 设置文件扩展名过滤,注意用双分号间隔 782 | if background_image: 783 | self.config['background'] = background_image 784 | self.config.write() 785 | self.label_top_background_image.setStyleSheet("""QLabel{ 786 | border:none; 787 | border-bottom-left-radius:0px; 788 | border-bottom-right-radius:0px; 789 | border-image:url('%s'); 790 | }""" % background_image) 791 | QApplication.processEvents() 792 | time.sleep(0.1) 793 | 794 | def font_size_(self): 795 | self.config = ConfigObj("config.ini", encoding="UTF8") 796 | font_size, ok = QInputDialog.getInt(self, "字体设置", "字体大小【整数】", int(self.config['font']['size']), 1, 70, 1) 797 | if ok: 798 | self.config['font']['size'] = font_size 799 | self.config.write() 800 | self.lineedit_input.setStyleSheet(""" 801 | QLineEdit{ 802 | color:%s; 803 | font-family:"Microsoft YaHei"; 804 | font-size:%spx; 805 | border-radius:0px; 806 | border-bottom:1px solid #E5E5E5; 807 | } 808 | QLineEdit:hover{ 809 | border-bottom:1px solid #C1C1C1; 810 | } 811 | QLineEdit:focus{ 812 | border-bottom:1px solid #12B7F5 813 | }""" % ( 814 | self.config['font']['color'], font_size)) 815 | 816 | def font_color_(self): 817 | self.config = ConfigObj("config.ini", encoding="UTF8") 818 | font_color = QColorDialog.getColor(Qt.blue, self, "信息框颜色") 819 | if font_color.name(): 820 | self.config['font']['color'] = font_color.name() 821 | self.config.write() 822 | self.lineedit_input.setStyleSheet(""" 823 | QLineEdit{ 824 | color:%s; 825 | font-family:"Microsoft YaHei"; 826 | font-size:%spx; 827 | border-radius:0px; 828 | border-bottom:1px solid #E5E5E5; 829 | } 830 | QLineEdit:hover{ 831 | border-bottom:1px solid #C1C1C1; 832 | } 833 | QLineEdit:focus{ 834 | border-bottom:1px solid #12B7F5 835 | }""" % ( 836 | font_color.name(), self.config['font']['size'])) 837 | 838 | def reset_(self): 839 | # 重置设置 840 | config_ini.config_() 841 | self.config = ConfigObj("config.ini", encoding='UTF8') 842 | self.width_ = int(self.config['size']['width']) 843 | self.height_ = int(self.config['size']['height']) 844 | self.size_window() 845 | self.lineedit_input.setStyleSheet(""" 846 | QLineEdit{ 847 | color:%s; 848 | font-family:"Microsoft YaHei"; 849 | font-size:%spx; 850 | border-radius:0px; 851 | border-bottom:1px solid #E5E5E5; 852 | } 853 | QLineEdit:hover{ 854 | border-bottom:1px solid #C1C1C1; 855 | } 856 | QLineEdit:focus{ 857 | border-bottom:1px solid #12B7F5 858 | }""" % ( 859 | self.config['font']['color'], self.config['font']['size'])) 860 | self.label_top_background_image.setStyleSheet("""QLabel{ 861 | border:none; 862 | border-bottom-left-radius:0px; 863 | border-bottom-right-radius:0px; 864 | border-image:url('./image/73114636.png'); 865 | }""") 866 | 867 | def quality_(self, name): 868 | # 画质设置 869 | 870 | self.config = ConfigObj("config.ini", encoding='UTF8') 871 | if name == "全部画质": 872 | self.quality_all.setChecked(True) 873 | self.quality_high.setChecked(False) 874 | self.quality_low.setChecked(False) 875 | self.config['quality'] = 'all' 876 | 877 | elif name == "最高画质": 878 | self.quality_high.setChecked(True) 879 | self.quality_all.setChecked(False) 880 | self.quality_low.setChecked(False) 881 | self.config['quality'] = 'high' 882 | 883 | elif name == "最低画质": 884 | self.quality_low.setChecked(True) 885 | self.quality_all.setChecked(False) 886 | self.quality_high.setChecked(False) 887 | self.config['quality'] = 'low' 888 | self.config.write() 889 | 890 | def open_path_(self): 891 | # 打开直播源目录 892 | if not os.path.exists(os.getcwd() + "/real_save"): # 判断文件夹是否创建 893 | os.makedirs(os.getcwd() + "/real_save") 894 | os.startfile(os.getcwd() + "/real_save") 895 | 896 | def open_dir_(self): 897 | # 打开软件目录 898 | os.startfile(os.getcwd()) 899 | 900 | def mouse_release_event_real_(self, event): 901 | action = self.menu_real.actionAt(event.pos()) 902 | if not action: 903 | # 没有找到action就交给QMenu自己处理 904 | return QtWidgets.QMenu.mouseReleaseEvent(self.menu_real, event) 905 | if action.property('canHide'): # 如果有该属性则给菜单自己处理 906 | return QtWidgets.QMenu.mouseReleaseEvent(self.menu_real, event) 907 | # 找到了QAction则只触发Action 908 | action.activate(action.Trigger) 909 | 910 | def mouse_play_event_real_(self, event): 911 | action = self.menu_play.actionAt(event.pos()) 912 | if not action: 913 | return QtWidgets.QMenu.mouseReleaseEvent(self.menu_play, event) 914 | if action.property('canHide'): 915 | return QtWidgets.QMenu.mouseReleaseEvent(self.menu_play, event) 916 | action.activate(action.Trigger) 917 | 918 | def mouse_release_event_quality_(self, event): 919 | action = self.menu_quality.actionAt(event.pos()) 920 | if not action: 921 | return QtWidgets.QMenu.mouseReleaseEvent(self.menu_quality, event) 922 | if action.property('canHide'): 923 | return QtWidgets.QMenu.mouseReleaseEvent(self.menu_quality, event) 924 | action.activate(action.Trigger) 925 | 926 | def add_shadow(self): 927 | # 添加阴影 928 | effect_shadow = QGraphicsDropShadowEffect(self) 929 | effect_shadow.setOffset(1, 1) # 偏移 930 | effect_shadow.setBlurRadius(10) # 阴影半径 931 | effect_shadow.setColor(QtCore.Qt.gray) # 阴影颜色 932 | self.setGraphicsEffect(effect_shadow) # 将设置套用到widget窗口中 933 | 934 | def config_ini(self): 935 | if not os.access("config.ini", os.F_OK): 936 | # 初始化config配置文件 937 | config_ini.config_() 938 | self.config = ConfigObj("config.ini", encoding='UTF8') 939 | self.width_ = int(int(self.config['size']['width']) * float(self.config['size']['mul'])) 940 | self.height_ = int(int(self.config['size']['height']) * float(self.config['size']['mul'])) 941 | self.setFixedSize(self.width_, self.height_) 942 | 943 | def size_(self): 944 | if not os.access("config.ini", os.F_OK): 945 | self.config_ini() 946 | config = ConfigObj("{}/config.ini".format(os.getcwd()), encoding='UTF8') 947 | if float(config['size']['mul']) >= 3: 948 | config['size']['mul'] = 1 949 | else: 950 | config['size']['mul'] = float(config['size']['mul']) + 0.5 951 | config['size']['width'] = 438 952 | config['size']['height'] = 338 953 | config.write() 954 | 955 | self.config = ConfigObj("config.ini", encoding='UTF8') 956 | self.width_ = int(int(self.config['size']['width']) * float(self.config['size']['mul'])) 957 | self.height_ = int(int(self.config['size']['height']) * float(self.config['size']['mul'])) 958 | self.size_window() 959 | 960 | def size_window(self): 961 | self.setFixedSize(self.width_, self.height_) 962 | self.label_background.setGeometry(QtCore.QRect(5, 0, self.width_ - 10, self.height_ - 10)) 963 | self.label_top_background.setFixedSize(self.width_ - 10, int(self.height_ / 2.7)) 964 | self.label_top_background_image.setFixedSize(self.width_ - 10, int(self.height_ / 2.7)) 965 | self.label_bottom_background.setFixedSize(self.label_top_background.width(), 966 | self.height_ - self.label_top_background.height() - 10) 967 | self.label_icon.setFixedSize(self.label_top_background.width(), 32) 968 | self.pushbutton_logo.setGeometry(int(self.width_ / 2 - 47 - 5), int(self.height_ / 2.7 - 47), 94, 94) 969 | self.pushbutton_get.setFixedSize(int(self.width_ - 194), 38) 970 | self.lineedit_input.setFixedSize(int(self.width_ - 194), 80) 971 | self.move((QtGui.QCursor.pos().x() - self.pushbutton_size.x() - 20), QtGui.QCursor.pos().y() - 16) 972 | 973 | def min_(self): # 最小化窗口 974 | self.showMinimized() 975 | 976 | def close_(self): # 关闭窗口动画 977 | subprocess.Popen("cmd.exe /C" + "taskkill -f -im N_m3u8DL-RE.exe", shell=True, stdout=subprocess.PIPE, 978 | stderr=subprocess.PIPE, universal_newlines=True, bufsize=1, creationflags=0x08000000) 979 | 980 | self.mSysTrayIcon = None # 软件关闭后托盘不会有残留 981 | self.close_animation = QPropertyAnimation(self, b"windowOpacity") 982 | self.close_animation.setDuration(400) # 动画时长 983 | self.close_animation.setStartValue(1) # 指定动作的初始状态 984 | self.close_animation.setEndValue(0) # 指定动作的最终状态 985 | self.close_animation.finished.connect(self.close) 986 | self.close_animation.start() 987 | 988 | def mousePressEvent(self, event): 989 | if event.button() == Qt.LeftButton: 990 | self.dragPosition = event.globalPos() - self.frameGeometry().topLeft() 991 | QApplication.postEvent(self, QEvent(174)) 992 | event.accept() 993 | 994 | def mouseMoveEvent(self, event): 995 | if event.buttons() == Qt.LeftButton: 996 | try: 997 | self.move(event.globalPos() - self.dragPosition) 998 | event.accept() 999 | except: 1000 | pass 1001 | 1002 | def keyPressEvent(self, event): # 键盘事件 1003 | if str(event.key()) == '16777216': 1004 | self.close_() 1005 | if str(event.key()) == '16777220' or str(event.key()) == '16777221': 1006 | self.thread_get() 1007 | 1008 | 1009 | if __name__ == "__main__": 1010 | app = QApplication(sys.argv) 1011 | translator = QTranslator() 1012 | translator.load('./qtbase_zh_CN.qm') 1013 | app.installTranslator(translator) 1014 | ui = MainUi() 1015 | ui.show() 1016 | sys.exit(app.exec_()) 1017 | -------------------------------------------------------------------------------- /chromedriver.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s282730788/RealTool/33db6f408b476d1f2ac35bf7ef22b37e7a986dd5/chromedriver.exe -------------------------------------------------------------------------------- /config_ini.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time:2022/10/13 12:00 3 | # @Author: Lanser 4 | # @File:config_ini.py 5 | # Software:PyCharm 6 | 7 | from configobj import ConfigObj 8 | import os 9 | 10 | 11 | def config_(): 12 | config = ConfigObj("%s/config.ini" % os.path.dirname(__file__), encoding='UTF8') 13 | config['size'] = {} 14 | config['size']['mul'] = 1 15 | config['size']['width'] = 438 16 | config['size']['height'] = 338 17 | config['background'] = "./image/73114636.png" 18 | config['quality'] = "all" 19 | config['font'] = {} 20 | config['font']['size'] = 26 21 | config['font']['color'] = '#505050' 22 | config['real'] = {'douyu': '1', 'huya': '1', 'bili': '1', 'douyin': '1', 'kuaishou':'1','yy': '1', 'kuwo': '1', 'kugou': '1', 'yizhibo': '1'} 23 | config['rid'] = "" 24 | config['player'] = 'pot' 25 | config['mpv'] = "" 26 | config.write() 27 | -------------------------------------------------------------------------------- /image/73114636.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s282730788/RealTool/33db6f408b476d1f2ac35bf7ef22b37e7a986dd5/image/73114636.png -------------------------------------------------------------------------------- /image/73114636_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s282730788/RealTool/33db6f408b476d1f2ac35bf7ef22b37e7a986dd5/image/73114636_bg.png -------------------------------------------------------------------------------- /image/close_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s282730788/RealTool/33db6f408b476d1f2ac35bf7ef22b37e7a986dd5/image/close_icon.png -------------------------------------------------------------------------------- /image/close_icon_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s282730788/RealTool/33db6f408b476d1f2ac35bf7ef22b37e7a986dd5/image/close_icon_2.png -------------------------------------------------------------------------------- /image/close_icon_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s282730788/RealTool/33db6f408b476d1f2ac35bf7ef22b37e7a986dd5/image/close_icon_3.png -------------------------------------------------------------------------------- /image/copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s282730788/RealTool/33db6f408b476d1f2ac35bf7ef22b37e7a986dd5/image/copy.png -------------------------------------------------------------------------------- /image/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s282730788/RealTool/33db6f408b476d1f2ac35bf7ef22b37e7a986dd5/image/icon.ico -------------------------------------------------------------------------------- /image/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s282730788/RealTool/33db6f408b476d1f2ac35bf7ef22b37e7a986dd5/image/icon.png -------------------------------------------------------------------------------- /image/image_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s282730788/RealTool/33db6f408b476d1f2ac35bf7ef22b37e7a986dd5/image/image_1.jpg -------------------------------------------------------------------------------- /image/image_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s282730788/RealTool/33db6f408b476d1f2ac35bf7ef22b37e7a986dd5/image/image_2.jpg -------------------------------------------------------------------------------- /image/image_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s282730788/RealTool/33db6f408b476d1f2ac35bf7ef22b37e7a986dd5/image/image_3.jpg -------------------------------------------------------------------------------- /image/image_4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s282730788/RealTool/33db6f408b476d1f2ac35bf7ef22b37e7a986dd5/image/image_4.jpg -------------------------------------------------------------------------------- /image/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s282730788/RealTool/33db6f408b476d1f2ac35bf7ef22b37e7a986dd5/image/logo.png -------------------------------------------------------------------------------- /image/logo_get.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s282730788/RealTool/33db6f408b476d1f2ac35bf7ef22b37e7a986dd5/image/logo_get.png -------------------------------------------------------------------------------- /image/lol_s12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s282730788/RealTool/33db6f408b476d1f2ac35bf7ef22b37e7a986dd5/image/lol_s12.png -------------------------------------------------------------------------------- /image/min_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s282730788/RealTool/33db6f408b476d1f2ac35bf7ef22b37e7a986dd5/image/min_icon.png -------------------------------------------------------------------------------- /image/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s282730788/RealTool/33db6f408b476d1f2ac35bf7ef22b37e7a986dd5/image/play.png -------------------------------------------------------------------------------- /image/rec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s282730788/RealTool/33db6f408b476d1f2ac35bf7ef22b37e7a986dd5/image/rec.png -------------------------------------------------------------------------------- /image/save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s282730788/RealTool/33db6f408b476d1f2ac35bf7ef22b37e7a986dd5/image/save.png -------------------------------------------------------------------------------- /image/setting_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s282730788/RealTool/33db6f408b476d1f2ac35bf7ef22b37e7a986dd5/image/setting_icon.png -------------------------------------------------------------------------------- /image/size_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s282730788/RealTool/33db6f408b476d1f2ac35bf7ef22b37e7a986dd5/image/size_icon.png -------------------------------------------------------------------------------- /qtbase_zh_CN.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s282730788/RealTool/33db6f408b476d1f2ac35bf7ef22b37e7a986dd5/qtbase_zh_CN.qm -------------------------------------------------------------------------------- /real/bili.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time:2022/10/14 0:35 3 | # @Author: Lanser 4 | # @File:bili.py 5 | # Software:PyCharm 6 | 7 | # 获取哔哩哔哩直播的真实流媒体地址,默认获取直播间提供的最高画质 8 | # qn=1500高清 9 | # qn=2500超清 10 | # qn=4000蓝光 11 | # qn=10000原画 12 | import requests 13 | import re 14 | import sys 15 | # sys.path.insert(0, '..') 16 | # from requests_code import requests_get_code 17 | from .requests_code import requests_get_code 18 | from multiprocessing.pool import ThreadPool 19 | 20 | 21 | class BiliBili: 22 | 23 | def __init__(self, rid): 24 | """ 25 | 有些地址无法在PotPlayer播放,建议换个播放器试试 26 | Args: 27 | rid: 28 | """ 29 | self.rid = rid 30 | self.header = { 31 | 'User-Agent': 'Mozilla/5.0 (iPod; CPU iPhone OS 14_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, ' 32 | 'like Gecko) CriOS/87.0.4280.163 Mobile/15E148 Safari/604.1', 33 | } 34 | 35 | def get_real_url(self): 36 | # 先获取直播状态和真实房间号 37 | r_url = 'https://api.live.bilibili.com/room/v1/Room/room_init' 38 | param = { 39 | 'id': self.rid 40 | } 41 | with requests.Session() as self.s: 42 | res = self.s.get(r_url, headers=self.header, params=param, timeout=2).json() 43 | if '不存在' in res['msg']: 44 | return {} 45 | live_status = res['data']['live_status'] 46 | if live_status != 1: 47 | return {} 48 | self.real_room_id = res['data']['room_id'] 49 | print(self.real_room_id) 50 | url = 'https://api.live.bilibili.com/xlive/web-room/v2/index/getRoomPlayInfo' 51 | param = { 52 | 'room_id': self.real_room_id, 53 | 'protocol': '0,1', 54 | 'format': '0,1,2', 55 | 'codec': '0,1', 56 | 'qn': 10000, 57 | 'platform': 'web', 58 | 'ptype': 8, 59 | } 60 | res = self.s.get(url, headers=self.header, params=param, timeout=2).json() 61 | uid = res['data']['uid'] 62 | stream_info = res['data']['playurl_info']['playurl']['stream'] 63 | accept_qn = stream_info[0]['format'][0]['codec'][0]['accept_qn'] 64 | real_lists = [] 65 | real_list = [] 66 | thread_list = [] 67 | real_dict = {} 68 | 69 | for data in stream_info: 70 | print(data) 71 | format_name = data['format'][0]['format_name'] 72 | if format_name == 'ts': 73 | base_url = data['format'][-1]['codec'][0]['base_url'] 74 | url_info = data['format'][-1]['codec'][0]['url_info'] 75 | for i, info in enumerate(url_info): 76 | for qn in accept_qn: 77 | url_ = base_url 78 | host = info['host'] 79 | extra = info['extra'] 80 | if qn < 10000: 81 | qn = qn * 10 82 | url_ = re.sub('bluray/index', f'{qn}/index', base_url) 83 | elif qn > 10000: 84 | continue 85 | extra = re.sub('qn=(\d+)', f'qn={qn}', extra) 86 | real_lists.append({f'线路{i + 1}_{qn}': f'{host}{url_}{extra}'}) 87 | break 88 | if real_lists: 89 | print(real_lists) 90 | pool = ThreadPool(processes=int(len(real_lists))) 91 | for real_ in real_lists: 92 | thread_list.append(pool.apply_async(requests_get_code, args=(real_,))) 93 | for thread in thread_list: 94 | return_dict = thread.get() 95 | if return_dict: 96 | real_list.append(return_dict) 97 | if real_list: 98 | try: 99 | real_list.append({'name': self.name_(uid)}) 100 | except Exception as err: 101 | print("匹配用户名失败:".format(err)) 102 | real_list.append({'name': uid}) 103 | 104 | real_list.append({'rid': self.rid}) 105 | 106 | real_dict['bili'] = real_list 107 | if real_dict: 108 | return real_dict 109 | return {} 110 | 111 | def name_(self, uid): 112 | url = 'https://api.bilibili.com/x/space/wbi/acc/info?mid={}&token=&platform=web'.format(uid) 113 | headers = { 114 | 'origin': 'https://space.bilibili.com', 115 | 'referer': 'https://space.bilibili.com/{}/'.format(uid), 116 | 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36', 117 | } 118 | response = requests.get(url, headers=headers, timeout=1).json() 119 | name = response['data']['name'] 120 | if response: 121 | return name 122 | else: 123 | return uid 124 | if __name__ == '__main__': 125 | rid = '6' 126 | bili = BiliBili(rid) 127 | print(bili.get_real_url()) 128 | -------------------------------------------------------------------------------- /real/douyin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time:2022/10/14 22:56 3 | # @Author: Lanser 4 | # @File:douyin.py 5 | # Software:PyCharm 6 | 7 | import re 8 | import json 9 | import requests 10 | import urllib.parse 11 | import sys 12 | # sys.path.insert(0, '..') 13 | from .requests_code import requests_get_code 14 | # from requests_code import requests_get_code 15 | from multiprocessing.pool import ThreadPool 16 | import requests_html 17 | 18 | 19 | class DouYin: 20 | def __init__(self, rid): 21 | self.rid = rid 22 | 23 | def get_real_url(self): 24 | 25 | if 'v.douyin.com' in self.rid: 26 | self.rid = self.get_room_id(self.rid) 27 | url = 'https://live.douyin.com/{}'.format(self.rid) 28 | headers = { 29 | "cookie": "__ac_nonce=000000000000000000000; ", 30 | # "cookie": "__ac_nonce=063e4d746007f686f2fcf; ", 31 | "referer": "https://live.douyin.com/", 32 | "upgrade-insecure-requests": "1", 33 | "user-agent": "Mozilla/5.0(WindowsNT10.0;WOW64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/86.0.4240.198Safari/537.36", 34 | } 35 | s = requests.session() 36 | response = s.get(url, headers=headers) 37 | 38 | text = urllib.parse.unquote( 39 | re.findall('', response.text)[0]) 40 | 41 | json_ = json.loads(text) 42 | try: 43 | name = json_['app']['initialState']['roomStore']['roomInfo']['room']['owner']['nickname'] 44 | except: 45 | name = self.rid 46 | douyin_list = [] 47 | douyin_dict = {} 48 | 49 | try: 50 | flv_pull_url = json_['app']['initialState']['roomStore']['roomInfo']['room']['stream_url']['flv_pull_url'] 51 | douyin_list.append(flv_pull_url) 52 | except: 53 | pass 54 | 55 | try: 56 | hls_pull_url_map = json_['app']['initialState']['roomStore']['roomInfo']['room']['stream_url'][ 57 | 'hls_pull_url_map'] 58 | lists_ = json_['app']['initialState']['roomStore']['roomInfo']['room']['stream_url']['live_core_sdk_data']['pull_data']['stream_data'] 59 | douyin_dict = json.loads(lists_)['data'] 60 | douyin_list.append(hls_pull_url_map) 61 | except: 62 | pass 63 | 64 | real_lists = [] 65 | real_list = [] 66 | thread_list = [] 67 | real_dict = {} 68 | 69 | # for real_ in douyin_list: 70 | # for name_ in real_: 71 | # if '.flv' in real_[name_]: 72 | # real_lists.append({f'flv_{name_}': real_[name_]}) 73 | # elif '.m3u8' in real_[name_]: 74 | # real_lists.append({f'm3u8_{name_}': real_[name_]}) 75 | 76 | for real_ in douyin_dict: 77 | try: 78 | real_lists.append({f'flv_{real_}': douyin_dict[real_]['main']['flv']}) 79 | real_lists.append({f'hls_{real_}': douyin_dict[real_]['main']['hls']}) 80 | except: 81 | pass 82 | 83 | if real_lists: 84 | pool = ThreadPool(processes=int(len(real_lists))) 85 | for real_ in real_lists: 86 | thread_list.append(pool.apply_async(requests_get_code, args=(real_,))) 87 | for thread in thread_list: 88 | return_dict = thread.get() 89 | if return_dict: 90 | real_list.append(return_dict) 91 | if real_list: 92 | real_list.append({'name': name}) 93 | real_list.append({'rid': self.rid}) 94 | real_dict['douyin'] = real_list 95 | if real_dict: 96 | return real_dict 97 | return {} 98 | 99 | def get_room_id(self, url): 100 | headers = { 101 | 'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1', 102 | } 103 | url = re.search(r'(https.*)', url).group(1) 104 | response = requests.head(url, headers=headers) 105 | url = response.headers['location'] 106 | room_id = re.search(r'\d{19}', url).group(0) 107 | 108 | headers.update({ 109 | 'cookie': '_tea_utm_cache_1128={%22utm_source%22:%22copy%22%2C%22utm_medium%22:%22android%22%2C%22utm_campaign%22:%22client_share%22}', 110 | 'host': 'webcast.amemv.com', 111 | }) 112 | params = ( 113 | ('type_id', '0'), 114 | ('live_id', '1'), 115 | ('room_id', room_id), 116 | ('app_id', '1128'), 117 | ('X-Bogus', '1'), 118 | ) 119 | 120 | response = requests.get('https://webcast.amemv.com/webcast/room/reflow/info/?', headers=headers, 121 | params=params).json() 122 | if response: 123 | return response['data']['room']['owner']['web_rid'] 124 | 125 | # if __name__ == '__main__': 126 | # r = '927264600913' 127 | # douyin = DouYin(r) 128 | # print(douyin.get_real_url()) 129 | -------------------------------------------------------------------------------- /real/douyu.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time:2022/10/13 18:34 3 | # @Author: Lanser 4 | # @File:douyu.py 5 | # Software:PyCharm 6 | 7 | # 获取斗鱼直播间的真实流媒体地址,默认最高画质 8 | # 使用 https://github.com/wbt5/real-url/issues/185 中两位大佬@wjxgzz @4bbu6j5885o3gpv6ss8找到的的CDN,在此感谢! 9 | import hashlib 10 | import json 11 | import re 12 | import time 13 | import sys 14 | from multiprocessing.pool import ThreadPool 15 | import execjs 16 | import requests 17 | 18 | # sys.path.insert(0, '..') 19 | from .requests_code import requests_get_code 20 | 21 | 22 | class DouYu: 23 | """ 24 | 可用来替换返回链接中的主机部分 25 | 两个阿里的CDN: 26 | dyscdnali1.douyucdn.cn 27 | dyscdnali3.douyucdn.cn 28 | 墙外不用带尾巴的akm cdn: 29 | hls3-akm.douyucdn.cn 30 | hlsa-akm.douyucdn.cn 31 | hls1a-akm.douyucdn.cn 32 | """ 33 | 34 | def __init__(self, rid): 35 | self.rate_list = [] 36 | """ 37 | 房间号通常为1~8位纯数字,浏览器地址栏中看到的房间号不一定是真实rid. 38 | Args: 39 | rid: 40 | """ 41 | self.did = '10000000000000000000000000001501' 42 | self.t10 = str(int(time.time())) 43 | self.t13 = str(int((time.time() * 1000))) 44 | self.rid = rid 45 | self.s = requests.Session() 46 | 47 | 48 | @staticmethod 49 | def md5(data): 50 | return hashlib.md5(data.encode('utf-8')).hexdigest() 51 | 52 | def get_pre(self): 53 | url = 'https://playweb.douyucdn.cn/lapi/live/hlsH5Preview/' + self.rid 54 | data = { 55 | 'rid': self.rid, 56 | 'did': self.did 57 | } 58 | auth = DouYu.md5(self.rid + self.t13) 59 | headers = { 60 | 'rid': self.rid, 61 | 'time': self.t13, 62 | 'auth': auth 63 | } 64 | res = self.s.post(url, headers=headers, data=data, timeout=2).json() 65 | error = res['error'] 66 | data = res['data'] 67 | try: 68 | for i in res['data']['settings']: 69 | self.rate_list.append(i) 70 | except: 71 | pass 72 | key = '' 73 | if data: 74 | rtmp_live = data['rtmp_live'] 75 | key = re.search( 76 | r'(\d{1,8}[0-9a-zA-Z]+)_?\d{0,4}(/playlist|.m3u8)', rtmp_live).group(1) 77 | return error, key 78 | 79 | def get_js(self): 80 | result = re.search( 81 | r'(function ub98484234.*)\s(var.*)', self.res).group() 82 | func_ub9 = re.sub(r'eval.*;}', 'strc;}', result) 83 | js = execjs.compile(func_ub9) 84 | res = js.call('ub98484234') 85 | 86 | v = re.search(r'v=(\d+)', res).group(1) 87 | rb = DouYu.md5(self.rid + self.did + self.t10 + v) 88 | 89 | func_sign = re.sub(r'return rt;}\);?', 'return rt;}', res) 90 | func_sign = func_sign.replace('(function (', 'function sign(') 91 | func_sign = func_sign.replace( 92 | 'CryptoJS.MD5(cb).toString()', '"' + rb + '"') 93 | 94 | js = execjs.compile(func_sign) 95 | params = js.call('sign', self.rid, self.did, self.t10) 96 | params += '&ver=219032101&rid={}&rate=-1'.format(self.rid) 97 | 98 | url = 'https://m.douyu.com/api/room/ratestream' 99 | res = self.s.post(url, params=params, timeout=2).text 100 | json_ = json.loads(res) 101 | try: 102 | for i in json_['data']['settings']: 103 | self.rate_list.append(i) 104 | except: 105 | pass 106 | key = re.search( 107 | r'(\d{1,8}[0-9a-zA-Z]+)_?\d{0,4}(.m3u8|/playlist)', res).group(1) 108 | return key 109 | 110 | def get_real_url(self): 111 | self.res = self.s.get('https://m.douyu.com/' + str(self.rid)).text 112 | result = re.search(r'rid":(\d{1,8}),"vipId', self.res) 113 | try: 114 | name = re.findall('"nickname":"(.*?)"', self.res)[0] 115 | except: 116 | name = self.rid 117 | if result: 118 | self.rid = result.group(1) 119 | else: 120 | return {} 121 | error, key = self.get_pre() 122 | if error == 0: 123 | pass 124 | elif error == 102: 125 | return False 126 | elif error == 104: 127 | return False 128 | else: 129 | key = self.get_js() 130 | 131 | real_lists = [] 132 | real_list = [] 133 | thread_list = [] 134 | real_dict = {} 135 | if not self.rate_list: 136 | self.rate_list = [{'name': '蓝光', 'rate': 0, 'high_bit': 1}, {'name': '超清', 'rate': 3, 'high_bit': 0}, 137 | {'name': '高清', 'rate': 2, 'high_bit': 0}] 138 | for rate in self.rate_list: 139 | if rate['rate'] != 0: 140 | flv = {"{}_flv".format(rate['name']): "http://hdltctwk.douyucdn2.cn/live/{}_{}.flv?uuid=".format(key, rate[ 141 | 'rate'] * 1000)} 142 | m3u8 = {"{}_m3u8".format(rate['name']): "http://hdltctwk.douyucdn2.cn/live/{}_{}.m3u8?uuid=".format(key, rate[ 143 | 'rate'] * 1000)} 144 | x_p2p = {"{}_x_p2p".format(rate['name']): "http://hdltctwk.douyucdn2.cn/live/{}_{}.xs?uuid=".format(key, 145 | rate[ 146 | 'rate'] * 1000)} 147 | aliyun = { 148 | "{}_aliyun".format(rate['name']): "http://dyscdnali1.douyucdn.cn/live/{}_{}.flv?uuid=".format(key, 149 | rate[ 150 | 'rate'] * 1000)} 151 | real_lists.append(flv) 152 | real_lists.append(m3u8) 153 | real_lists.append(x_p2p) 154 | real_lists.append(aliyun) 155 | else: 156 | flv = {"{}_flv".format(rate['name']): "http://hdltctwk.douyucdn2.cn/live/{}.flv?uuid=".format(key)} 157 | m3u8 = {"{}_m3u8".format(rate['name']): "http://hdltctwk.douyucdn2.cn/live/{}.m3u8?uuid=".format(key)} 158 | x_p2p = {"{}_x_p2p".format(rate['name']): "http://hdltctwk.douyucdn2.cn/live/{}.xs?uuid=".format(key)} 159 | aliyun = { 160 | "{}_aliyun".format(rate['name']): "http://dyscdnali1.douyucdn.cn/live/{}.flv?uuid=".format(key)} 161 | real_lists.append(flv) 162 | real_lists.append(m3u8) 163 | real_lists.append(x_p2p) 164 | real_lists.append(aliyun) 165 | if real_lists: 166 | pool = ThreadPool(processes=int(len(real_lists))) 167 | for real_ in real_lists: 168 | thread_list.append(pool.apply_async(requests_get_code, args=(real_,))) 169 | for thread in thread_list: 170 | return_dict = thread.get() 171 | if return_dict: 172 | real_list.append(return_dict) 173 | if real_list: 174 | real_list.append({'name': name}) 175 | real_list.append({'rid': self.rid}) 176 | real_dict['douyu'] = real_list 177 | return real_dict 178 | return {} 179 | 180 | # if __name__ == '__main__': 181 | # r = '101' 182 | # # r = input('输入斗鱼直播间号:\n') 183 | # s = DouYu(r) 184 | # print(s.get_real_url()) 185 | -------------------------------------------------------------------------------- /real/huya.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time:2022/10/13 18:34 3 | # @Author: Lanser 4 | # @File:huya.py 5 | # Software:PyCharm 6 | 7 | # 获取虎牙直播的真实流媒体地址。 8 | 9 | import requests 10 | import re 11 | import base64 12 | import urllib.parse 13 | import hashlib 14 | import time 15 | import sys 16 | from .requests_code import requests_get_code 17 | from multiprocessing.pool import ThreadPool 18 | 19 | class HuYa: 20 | def __init__(self, rid): 21 | self.rid = rid 22 | 23 | def live(self, e): 24 | i, b = e.split('?') 25 | r = i.split('/') 26 | s = re.sub(r'.(flv|m3u8)', '', r[-1]) 27 | c = b.split('&', 3) 28 | c = [i for i in c if i != ''] 29 | n = {i.split('=')[0]: i.split('=')[1] for i in c} 30 | fm = urllib.parse.unquote(n['fm']) 31 | u = base64.b64decode(fm).decode('utf-8') 32 | p = u.split('_')[0] 33 | f = str(int(time.time() * 1e7)) 34 | l = n['wsTime'] 35 | t = '0' 36 | h = '_'.join([p, t, s, f, l]) 37 | m = hashlib.md5(h.encode('utf-8')).hexdigest() 38 | y = c[-1] 39 | url = "{}?wsSecret={}&wsTime={}&u={}&seqid={}&{}".format(i, m, l, t, f, y) 40 | return url 41 | 42 | def get_real_url(self): 43 | real_lists = [] 44 | real_list = [] 45 | thread_list = [] 46 | real_dict = {} 47 | name = '' 48 | try: 49 | room_url = 'https://m.huya.com/{}'.format(self.rid) 50 | header = { 51 | 'Content-Type': 'application/x-www-form-urlencoded', 52 | 'User-Agent': 'Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) ' 53 | 'Chrome/75.0.3770.100 Mobile Safari/537.36 ' 54 | } 55 | response = requests.get(url=room_url, headers=header, timeout=2).text 56 | 57 | try: 58 | name = re.findall('"sNick":"(.*?)","iSex"', response)[0] 59 | except: 60 | name = self.rid 61 | liveLineUrl = re.findall(r'"liveLineUrl":"([\s\S]*?)",', response)[0] 62 | liveline = base64.b64decode(liveLineUrl).decode('utf-8') 63 | 64 | if liveline: 65 | if 'replay' in liveline: 66 | real_lists.append({'直播录像': f'https://{liveline}'}) 67 | else: 68 | liveline = self.live(liveline) 69 | real_url_flv = ("https:" + liveline).replace("hls", "flv").replace("m3u8", "flv").replace( 70 | '&ctype=tars_mobile', '') 71 | print(real_url_flv) 72 | rate = [500, 2000, 4000, 8000, 10000] 73 | for ratio in range(len(rate) - 1, -1, -1): 74 | ratio = rate[ratio] 75 | if ratio != 10000: 76 | real_url_flv = real_url_flv.replace('.flv?', f'.flv?ratio={ratio}&') 77 | real_lists.append({f'flv_{ratio}': real_url_flv}) 78 | else: 79 | real_lists.append({f'flv_{ratio}': real_url_flv}) 80 | except: 81 | pass 82 | if real_lists: 83 | pool = ThreadPool(processes=int(len(real_lists))) 84 | for real_ in real_lists: 85 | thread_list.append(pool.apply_async(requests_get_code, args=(real_,))) 86 | for thread in thread_list: 87 | return_dict = thread.get() 88 | if return_dict: 89 | real_list.append(return_dict) 90 | if real_list: 91 | real_list.append({'name': name}) 92 | real_list.append({'rid': self.rid}) 93 | real_dict['huya'] = real_list 94 | if real_dict: 95 | return real_dict 96 | return {} 97 | 98 | 99 | # rid = input('输入虎牙直播房间号:\n') 100 | # real_url = HuYa(rid) 101 | # print('该直播间源地址为:') 102 | # print(real_url.get_real_url()) 103 | -------------------------------------------------------------------------------- /real/kugou.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time:2022/10/23 6:07 3 | # @Author: Lanser 4 | # @File:kugou.py 5 | # Software:PyCharm 6 | # 获取酷狗繁星直播的真实流媒体地址,默认最高码率。 7 | import re 8 | 9 | import requests 10 | import sys 11 | # sys.path.insert(0, '..') 12 | from .requests_code import requests_get_code 13 | from multiprocessing.pool import ThreadPool 14 | 15 | 16 | class KuGou: 17 | 18 | def __init__(self, rid): 19 | """ 20 | 酷狗繁星直播 21 | Args: 22 | rid: 房间号 23 | """ 24 | self.rid = rid 25 | self.s = requests.Session() 26 | self.url = 'https://fx1.service.kugou.com/video/mo/live/pull/h5/v3/streamaddr' 27 | 28 | def get_real_url(self): 29 | params = { 30 | 'roomId': self.rid, 31 | 'platform': 18, 32 | 'version': 1000, 33 | 'streamType': '3-6', 34 | 'liveType': 1, 35 | 'ch': 'fx', 36 | 'ua': 'fx-mobile-h5', 37 | 'kugouId': 0, 38 | 'layout': 1 39 | } 40 | real_lists = [] 41 | real_list = [] 42 | thread_list = [] 43 | real_dict = {} 44 | try: 45 | name = self.name(self.rid) 46 | except: 47 | name = self.rid 48 | try: 49 | res = self.s.get(self.url, params=params, timeout=2).json() 50 | if res['code'] == 1: 51 | return {} 52 | try: 53 | real_data_horizontal = res['data']['horizontal'] 54 | real_data_vertical = res['data']['vertical'] 55 | if real_data_horizontal: 56 | real_lists.append({'hls': real_data_horizontal[0]['hls'][0]}) 57 | real_lists.append({'hls': real_data_horizontal[0]['httpshls'][0]}) 58 | if real_data_vertical: 59 | real_lists.append({'hls': real_data_vertical[0]['hls'][0]}) 60 | real_lists.append({'httpshls': real_data_vertical[0]['httpshls'][0]}) 61 | except: 62 | return {} 63 | 64 | if real_lists: 65 | pool = ThreadPool(processes=int(len(real_lists))) 66 | for real_ in real_lists: 67 | thread_list.append(pool.apply_async(requests_get_code, args=(real_,))) 68 | for thread in thread_list: 69 | return_dict = thread.get() 70 | if return_dict: 71 | real_list.append(return_dict) 72 | if real_list: 73 | real_list.append({'name': name}) 74 | real_list.append({'rid': self.rid}) 75 | real_dict['kugou'] = real_list 76 | return real_dict 77 | return {} 78 | except IndexError: 79 | try: 80 | url = f'https://fx1.service.kugou.com/biz/ChannelVServices/' \ 81 | f'RoomLiveService.RoomLiveService.getCurrentLiveStarForMob/{self.rid}' 82 | res = self.s.get(url).json() 83 | if res['code'] == 1: 84 | return {} 85 | roomid = res['data']['roomId'] 86 | self.url = 'https://fx2.service.kugou.com/video/pc/live/pull/mutiline/streamaddr' 87 | params = { 88 | 'std_rid': roomid, 89 | 'version': '1.0', 90 | 'streamType': '1-2-3-5-6', 91 | 'targetLiveTypes': '1-4-5-6', 92 | 'ua': 'fx-h5' 93 | } 94 | res = self.s.get(self.url, params=params, timeout=2).json() 95 | real_url = res.get('data').get('lines')[-1].get('streamProfiles')[-1]['httpsHls'][-1] 96 | real_dict['kugou'] = [{'hls': real_url}, {'name': name}, {'rid': self.rid}] 97 | return real_dict 98 | except: 99 | return {} 100 | 101 | def name(self, uid): 102 | headers = {'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36'} 103 | url = 'https://fanxing.kugou.com/{}'.format(uid) 104 | response = requests.get(url, headers=headers).text 105 | name = re.findall("starName:'(.*?)'", response)[0] 106 | return name 107 | 108 | # if __name__ == '__main__': 109 | # rid = '6738148' 110 | # # rid = '2678075' 111 | # kugou = KuGou(rid) 112 | # print(kugou.get_real_url()) 113 | -------------------------------------------------------------------------------- /real/kuwo.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time:2022/10/23 5:04 3 | # @Author: Lanser 4 | # @File:kuwo.py 5 | # Software:PyCharm 6 | 7 | # 酷我聚星直播:http://jx.kuwo.cn/ 8 | 9 | import requests 10 | import re 11 | import sys 12 | # sys.path.insert(0, '..') 13 | from .requests_code import requests_get_code 14 | from multiprocessing.pool import ThreadPool 15 | 16 | class KuWo: 17 | 18 | def __init__(self, rid): 19 | self.rid = rid 20 | self.BASE_URL = 'https://jxm0.kuwo.cn/video/mo/live/pull/h5/v3/streamaddr' 21 | self.s = requests.Session() 22 | 23 | def get_real_url(self): 24 | res = self.s.get(f'https://jx.kuwo.cn/{self.rid}').text 25 | roomid = re.search(r"roomId: '(\d*)'", res) 26 | try: 27 | name = re.findall("starName: '(.*?)'", res)[0] 28 | except: 29 | name = self.rid 30 | if roomid: 31 | self.rid = roomid.group(1) 32 | else: 33 | return {} 34 | params = { 35 | 'std_bid': 1, 36 | 'roomId': self.rid, 37 | 'platform': 405, 38 | 'version': 1000, 39 | 'streamType': '3-6', 40 | 'liveType': 1, 41 | 'ch': 'fx', 42 | 'ua': 'fx-mobile-h5', 43 | 'kugouId': 0, 44 | 'layout': 1, 45 | 'videoAppId': 10011, 46 | } 47 | res = self.s.get(self.BASE_URL, params=params, timeout=2).json() 48 | real_lists = [] 49 | real_list = [] 50 | thread_list = [] 51 | real_dict = {} 52 | if res['data']['sid'] == -1: 53 | return {} 54 | try: 55 | real_url = res['data']['horizontal'][0]['httpshls'][0] 56 | except (KeyError, IndexError): 57 | real_url = res['data']['vertical'][0]['httpshls'][0] 58 | real_lists.append({f'httpshls': real_url}) 59 | if real_lists: 60 | pool = ThreadPool(processes=int(len(real_lists))) 61 | for real_ in real_lists: 62 | thread_list.append(pool.apply_async(requests_get_code, args=(real_,))) 63 | for thread in thread_list: 64 | return_dict = thread.get() 65 | if return_dict: 66 | real_list.append(return_dict) 67 | if real_list: 68 | real_list.append({'name': name}) 69 | real_list.append({'rid': self.rid}) 70 | real_dict['kuwo'] = real_list 71 | return real_dict 72 | return {} 73 | 74 | 75 | 76 | # if __name__ == '__main__': 77 | # r = 'https://x.kuwo.cn/32067302?refer=2193' 78 | # if 'kuwo.cn' in r: 79 | # r = re.findall('/(\d+)', r)[0] 80 | # print(r) 81 | # kuwo = KuWo(r) 82 | # print(kuwo.get_real_url()) 83 | -------------------------------------------------------------------------------- /real/requests_code.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time:2022/10/24 3:21 3 | # @Author: Lanser 4 | # @File:requests_code.py 5 | # Software:PyCharm 6 | 7 | import requests 8 | 9 | 10 | def requests_get_code(real_dict): 11 | for real_ in real_dict: 12 | try: 13 | code = requests.get(real_dict[real_], stream=True, timeout=1).status_code 14 | if code == 200: 15 | return real_dict 16 | except: 17 | pass -------------------------------------------------------------------------------- /real/rquests_html_1.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time: 2023/1/15 20:08 3 | # @Author: 须尽欢 4 | # @File: rquests_html_1.py 5 | # @Contact: 282730788@qq.com 6 | # Software: PyCharm 7 | 8 | from requests_html import HTMLSession 9 | # import requests 10 | session = HTMLSession() 11 | headers = { 12 | "cookie": "__ac_nonce=0;", 13 | "referer": "https://live.douyin.com/", 14 | "upgrade-insecure-requests": "1", 15 | "user-agent": "Mozilla/5.0(WindowsNT10.0;WOW64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/86.0.4240.198Safari/537.36", 16 | } 17 | 18 | # headers = {"Host": "v.douyin.com", 19 | # "Connection": "keep-alive", 20 | # "sec-ch-ua": "Not_A Brand;v=99, Google Chrome;v=109, Chromium;v=109", 21 | # "sec-ch-ua-mobile": "?0", 22 | # "sec-ch-ua-platform": "Windows", 23 | # "Upgrade-Insecure-Requests": "1", 24 | # "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36", 25 | # "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", 26 | # "Sec-Fetch-Site": "none", 27 | # "Sec-Fetch-Mode": "navigate", 28 | # "Sec-Fetch-User": "?1", 29 | # "Sec-Fetch-Dest": "document", 30 | # "Accept-Encoding": "gzip, deflate, br", 31 | # 32 | # "Cookie": "ttwid=1%7Cy7G52XRGM50D_HvVEITMI0Zo9VEqZhMOe9j0lcjJpwI%7C1673787944%7C930453440494f53980e0b938e860106d83e598f8bd3442e85d730260b7441ba0; odin_tt=659faadea0dac0c524a727a98ff51a3b75fe39adc297a932258cf5c5a0663ab61b7b63f59ea7cc540a97c382f7ba7076ccaad5cee4bfdee043ad622d0cb7f003cd2313362989c49c0de1ff7fe715bd1c; msToken=rLEW5ugOkgF6LMV-A0lWKEhIpDDZhk-EpSQbMgGb6n4GIAOnDeVmBBmn6fC80Ktd8xNVyAFOhEYjzsSTRqNBm1g6i08iuYGAqDTg_XXiSO3MmTXlY3w6; live_can_add_dy_2_desktop=%221%22" 33 | # } 34 | 35 | url = 'https://v.douyin.com/kQAd2h' 36 | 37 | response = session.head(url=url, headers=headers) 38 | 39 | print(response.headers) 40 | -------------------------------------------------------------------------------- /real/yizhibo.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time:2022/11/2 23:50 3 | # @Author: Lanser 4 | # @File:yizhibo.py 5 | # Software:PyCharm 6 | 7 | # 获取一直播的真实流媒体地址。 8 | 9 | import requests 10 | import re 11 | 12 | 13 | class YiZhiBo: 14 | 15 | def __init__(self, rid): 16 | """ 17 | 一直播需要传入直播间的完整地址 18 | Args: 19 | rid:完整地址 20 | """ 21 | self.rid = rid 22 | self.s = requests.Session() 23 | 24 | def get_real_url(self): 25 | try: 26 | url = 'https://www.yizhibo.com/l/{}.html'.format(self.rid) 27 | real_list = [] 28 | real_dict = {} 29 | res = self.s.get(url).text 30 | play_url, name, status_code, memberid = \ 31 | re.findall(r'play_url:"(.*?)"[\s\S]*nickname:"(.*?)"[\s\S]*status:(\d+)[\s\S]*memberid:(.*?),', res)[0] 32 | if status_code == '10': 33 | real_list.append({'m3u8': f'{play_url}'}) 34 | real_list.append({'name': f'{name}'}) 35 | real_list.append({'rid': f'{memberid}'}) 36 | real_dict['yizhibo'] = real_list 37 | return real_dict 38 | else: 39 | return {} 40 | except Exception: 41 | return {} 42 | 43 | -------------------------------------------------------------------------------- /real/yy.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time:2022/10/23 0:39 3 | # @Author: Lanser 4 | # @File:yy.py 5 | # Software:PyCharm 6 | 7 | # 获取YY直播的真实流媒体地址。https://www.yy.com/1349606469 8 | # 默认获取最高画质 9 | import time 10 | 11 | import requests 12 | import re 13 | import json 14 | import sys 15 | # sys.path.insert(0, '..') 16 | from .requests_code import requests_get_code 17 | from multiprocessing.pool import ThreadPool 18 | 19 | 20 | class YY: 21 | 22 | def __init__(self, rid): 23 | self.rid = rid 24 | 25 | def get_real_url(self): 26 | 27 | headers_web = { 28 | 'referer': f'https://www.yy.com/', 29 | 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36 ' 30 | } 31 | headers = { 32 | 'referer': f'https://wap.yy.com/mobileweb/{self.rid}', 33 | 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) ' 34 | 'Chrome/95.0.4638.69 Safari/537.36 ' 35 | } 36 | name = '' 37 | real_lists = [] 38 | real_list = [] 39 | thread_list = [] 40 | real_dict = {} 41 | try: 42 | url = 'https://www.yy.com/{}'.format(self.rid) 43 | response = requests.get(url, headers=headers_web, timeout=2) 44 | name = re.findall('

(.*?)

', response.text)[0] 45 | except: 46 | name = self.rid 47 | 48 | def room_id_(): 49 | url = 'https://www.yy.com/{}'.format(self.rid) 50 | with requests.Session() as s: 51 | response = s.get(url, headers=headers_web, timeout=2) 52 | if response.status_code == 200: 53 | room_id = re.findall('ssid : "(\d+)', response.text)[0] 54 | return room_id 55 | 56 | try: 57 | room_url = f'https://interface.yy.com/hls/new/get/{self.rid}/{self.rid}/1200?source=wapyy&callback=' 58 | with requests.Session() as s: 59 | res = s.get(room_url, headers=headers, timeout=2) 60 | except: 61 | try: 62 | room_id = room_id_() 63 | room_url = f'https://interface.yy.com/hls/new/get/{room_id}/{room_id}/1200?source=wapyy&callback=' 64 | with requests.Session() as s: 65 | res = s.get(room_url, headers=headers, timeout=2) 66 | except: 67 | return {} 68 | 69 | if res.status_code == 200: 70 | data = json.loads(res.text[1:-1]) 71 | if data.get('hls', 0): 72 | xa = data['audio'] 73 | xv = data['video'] 74 | xv = re.sub(r'_0_\d+_0', '_0_0_0', xv) 75 | url = f'https://interface.yy.com/hls/get/stream/15013/{xv}/15013/{xa}?source=h5player&type=m3u8' 76 | res = s.get(url).json() 77 | real_url = res['hls'] 78 | real_lists.append({'hls': real_url}) 79 | if real_lists: 80 | pool = ThreadPool(processes=int(len(real_lists))) 81 | for real_ in real_lists: 82 | thread_list.append(pool.apply_async(requests_get_code, args=(real_,))) 83 | for thread in thread_list: 84 | return_dict = thread.get() 85 | if return_dict: 86 | real_list.append(return_dict) 87 | if real_list: 88 | real_list.append({'name': name}) 89 | real_list.append({'rid': self.rid}) 90 | real_dict['yy'] = real_list 91 | return real_dict 92 | return {} 93 | 94 | # if __name__ == '__main__': 95 | # r = '54880976' 96 | # yy = YY(r) 97 | # 98 | # # r = input('输入YY直播房间号:\n') 99 | # print( yy.get_real_url()) 100 | --------------------------------------------------------------------------------