diff --git a/biliup/engine/download.py b/biliup/engine/download.py index 6b102be..e82617b 100644 --- a/biliup/engine/download.py +++ b/biliup/engine/download.py @@ -19,30 +19,32 @@ class DownloadBase: # -c copy -bsf:a aac_adtstoasc -movflags +faststart output.mp4 self.raw_stream_url = None self.opt_args = [] + self.default_output_args = [ '-bsf:a', 'aac_adtstoasc', ] + if config.get('segment_time'): + self.default_output_args += \ + ['-segment_time', f"{config.get('segment_time') if config.get('segment_time') else '00:50:00'}"] + else: + self.default_output_args += \ + ['-fs', f"{config.get('file_size') if config.get('file_size') else '2621440000'}"] self.default_input_args = ['-headers', ''.join('%s: %s\r\n' % x for x in fake_headers.items()), '-reconnect_streamed', '1', '-reconnect_delay_max', '20', '-rw_timeout', '20000000'] - if config.get('file_size'): - self.default_type_args = ['-fs', f"{config.get('file_size') if config.get('file_size') else '2621440000'}"] - self.default_type_num = 1 - elif config.get('segment_time'): - self.default_type_args = ['-segment_time', f"{config.get('segment_time') if config.get('segment_time') else '00:50:00'}"] - self.default_type_num = 2 def check_stream(self): logger.debug(self.fname) raise NotImplementedError() def download(self, filename): - if self.default_type_num == 1: - typenum = [f'{filename}.part'] - elif self.default_type_num == 2: - typenum = ['-f', 'segment', f'{self.file_name}-%03d.{self.suffix}'] args = ['ffmpeg', '-y', *self.default_input_args, '-i', self.raw_stream_url, *self.default_output_args, *self.opt_args, - '-c', 'copy', '-f', self.suffix, *self.default_type_args, *typenum] + '-c', 'copy', '-f', self.suffix] + if config.get('segment_time'): + args += ['-f', 'segment', f'{self.fname}-%03d.{self.suffix}'] + else: + args += [f'{filename}.part'] + proc = subprocess.Popen(args, stdin=subprocess.PIPE) try: retval = proc.wait() diff --git a/biliup/plugins/bili_chromeup.py b/biliup/plugins/bili_chromeup.py index fd974a8..9021d1c 100644 --- a/biliup/plugins/bili_chromeup.py +++ b/biliup/plugins/bili_chromeup.py @@ -1,17 +1,20 @@ import json import os +import random import time import selenium.common +from PIL import Image from selenium import webdriver +from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys +from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support import expected_conditions as ec -from selenium.webdriver.support.wait import WebDriverWait +from selenium.webdriver.support.ui import WebDriverWait from biliup import Plugin, config from ..engine.upload import UploadBase, logger -from .slider import slider_cracker @Plugin.upload("bilibili") @@ -201,4 +204,149 @@ class BiliChrome(UploadBase): '//*[@class="upload-v2-container"]/div[2]/div[3]/div[1]/div[12]/div[2]/div/textarea') text_2.send_keys('职业选手直播第一视角录像。这个自动录制上传的小程序开源在Github:' 'http://t.cn/RgapTpf(或者在Github搜索ForgQi)\n' - '交流群:837362626') \ No newline at end of file + '交流群:837362626') + +class slider_cracker(object): + def __init__(self, driver): + self.driver = driver + self.driver.maximize_window() # 最大化窗口 + self.driver.set_window_size(1024, 768) + self.fn = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'true_image.png') + + def get_true_image(self, slider_xpath=r'//*[@id="gc-box"]/div/div[3]/div[1]'): + # element = WebDriverWait(self.driver, 50).until(EC.element_to_be_clickable((By.XPATH, slider_xpath))) + element = WebDriverWait(self.driver, 50).until( + EC.element_to_be_clickable((By.CLASS_NAME, "geetest_slider_button"))) + ActionChains(self.driver).move_to_element(element).perform() # 鼠标移动到滑动框以显示图片 + js = 'document.querySelector("body > div.geetest_panel.geetest_wind ' \ + '> div.geetest_panel_box.geetest_no_logo.geetest_panelshowslide ' \ + '> div.geetest_panel_next > div > div.geetest_wrap > div.geetest_widget ' \ + '> div > a > div.geetest_canvas_img.geetest_absolute > canvas").' \ + 'style.display = "%s";' + self.driver.execute_script(js % "inline") + time.sleep(1) + true_image = self.get_img(self.fn) + self.driver.execute_script(js % "none") + return true_image + + def get_img(self, img_name, img_xpath=r'//*[@id="gc-box"]/div/div[1]/div[2]/div[1]/a[2]'): # 260*116 + fn = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'slider_screenshot.png') + + screen_shot = self.driver.save_screenshot(fn) + # image_element = self.driver.find_element_by_xpath(img_xpath) + image_element = self.driver.find_element_by_class_name(r'geetest_window') + left = image_element.location['x'] + top = image_element.location['y'] # selenium截图并获取验证图片location后将其截出保存 + right = image_element.location['x'] + image_element.size['width'] + bottom = image_element.location['y'] + image_element.size['height'] + image = Image.open(fn) + image = image.crop((left, top, right, bottom)) + image.save(img_name) + return image + + def analysis(self, true_image, knob_xpath=r'//*[@id="gc-box"]/div/div[3]/div[2]'): + fn = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'img2.png') + img1 = Image.open(self.fn) + # slider_element = self.driver.find_element_by_xpath(knob_xpath) + slider_element = self.driver.find_element_by_class_name("geetest_slider_button") + ActionChains(self.driver).click_and_hold(slider_element).perform() # 点击滑块后截取残缺图 + time.sleep(1) + img2 = self.get_img(fn) + img1_width, img1_height = img1.size + img2_width, img2_height = img2.size + left = 0 + flag = False + + for i in range(69, img1_width): # 遍历x>65的像素点(x<65是拼图块) + for j in range(0, img1_height): + if not self.is_pixel_equal(img1, img2, i, j): + left = i + flag = True + break + if flag: + break + if left >= 73: + left = left - 3 # 误差纠正 + else: + left = left + return left + + def is_pixel_equal(self, img1, img2, x, y): # 通过比较俩图片像素点RGB值差值判断是否为缺口 + pix1 = img1.load()[x, y] + pix2 = img2.load()[x, y] + if (abs(pix1[0] - pix2[0] < 60) and abs(pix1[1] - pix2[1] < 60) and abs(pix1[2] - pix2[2] < 60)): + return True + else: + return False + + def get_track(self, distance): + """ + 根据偏移量获取移动轨迹 + :param distance: 偏移量 + :return: 移动轨迹 + """ + # 移动轨迹 + track = [] + # 当前位移 + current = 0 + # 减速阈值 + mid = distance * 4 / 5 + # 计算间隔 + t = 0.2 + # 初速度 + v = 0 + + while current < distance: + if current < mid: + # 加速度为正2 + a = 2 + else: + # 加速度为负3 + a = -3 + # 初速度v0 + v0 = v + # 当前速度v = v0 + at + v = v0 + a * t + # 移动距离x = v0t + 1/2 * a * t^2 + move = v0 * t + 1 / 2 * a * t * t + # 当前位移 + current += move + # 加入轨迹 + track.append(round(move)) + # print(track) + # + # print(sum(track)) + track.append(distance - sum(track)) + # print(track) + # print(sum(track)) + return track + + def move_to_gap(self, slider, track): + """ + 拖动滑块到缺口处 + :param slider: 滑块 + :param track: 轨迹 + :return: + """ + ActionChains(self.driver).click_and_hold(slider).perform() + for x in track: + ActionChains(self.driver).move_by_offset(xoffset=x, yoffset=random.uniform(-5, 2)).perform() + time.sleep(0.5) + ActionChains(self.driver).release().perform() + + def crack(self): + true_image = self.get_true_image() + x_offset = self.analysis(true_image) + print(x_offset) + + track = self.get_track(x_offset) + knob_element = WebDriverWait(self.driver, 50).until( + EC.element_to_be_clickable((By.XPATH, r'/html/body/div[2]/div[2]/div[6]/div/div[1]/div[2]/div[2]'))) + self.move_to_gap(knob_element, track) + + # fn = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'result0.png') + # fn1 = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'result1.png') + # time.sleep(0.02) + # screen_shot = self.driver.save_screenshot(fn) + # time.sleep(2) + # screen_shot = self.driver.save_screenshot(fn1) diff --git a/biliup/plugins/bilibili.py b/biliup/plugins/bilibili.py index 0d256f3..41f04c6 100644 --- a/biliup/plugins/bilibili.py +++ b/biliup/plugins/bilibili.py @@ -1,5 +1,8 @@ -from ykdl.common import url_to_module +import random +import requests + +from . import match1, logger from ..engine.decorators import Plugin from ..engine.download import DownloadBase @@ -10,12 +13,43 @@ class Bilibili(DownloadBase): super().__init__(fname, url, suffix) def check_stream(self): - site, url = url_to_module(self.url) - try: - info = site.parser(url) - except AssertionError: + rid = match1(self.url, r'/(\d+)') + api1_data = requests.get(f"https://api.live.bilibili.com/room/v1/Room/room_init?id={rid}").json() + if api1_data['code'] == 0: + vid = api1_data['data']['room_id'] + else: + logger.info('Get room ID from API failed: %s', api1_data['msg']) + vid = rid + api2_data = requests.get(f"https://api.live.bilibili.com/room/v1/Room/get_info?room_id={vid}").json() + if api2_data['code'] != 0: + logger.debug(api2_data['msg']) return False - stream_id = info.stream_types[0] - urls = info.streams[stream_id]['src'] - self.raw_stream_url = urls[0] + api2_data = api2_data['data'] + if api2_data['live_status'] != 1: + logger.debug(api2_data['msg']) + return False + title = api2_data['title'] + api3_data = \ + requests.get(f"https://api.live.bilibili.com/live_user/v1/UserInfo/get_anchor_in_room?roomid={vid}").json() + if api3_data['code'] == 0: + artist = api3_data['data']['info']['uname'] + title = '{} - {}'.format(title, artist) + logger.debug(title) + params = { + 'https_url_req': 1, + 'cid': vid, + 'platform': 'web', + 'qn': 10000, + 'ptype': '16' + } + data = requests.get("https://api.live.bilibili.com/xlive/web-room/v1/playUrl/playUrl", params=params).json() + if data['code'] != 0: + logger.debug(data['msg']) + return False + data = data['data'] + qlt = data['current_qn'] + aqlts = {x['qn']: x['desc'] for x in data['quality_description']} + logger.debug(qlt) + logger.debug(aqlts) + self.raw_stream_url = random.choice(data['durl'])['url'] return True diff --git a/biliup/plugins/slider/__init__.py b/biliup/plugins/slider/__init__.py deleted file mode 100644 index eb6cd25..0000000 --- a/biliup/plugins/slider/__init__.py +++ /dev/null @@ -1,155 +0,0 @@ -import os -import random -import time - -from PIL import Image -from selenium.webdriver.common.action_chains import ActionChains -from selenium.webdriver.common.by import By -from selenium.webdriver.support import expected_conditions as EC -from selenium.webdriver.support.ui import WebDriverWait - - -class slider_cracker(object): - def __init__(self, driver): - self.driver = driver - self.driver.maximize_window() # 最大化窗口 - self.driver.set_window_size(1024, 768) - self.fn = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'true_image.png') - - def get_true_image(self, slider_xpath=r'//*[@id="gc-box"]/div/div[3]/div[1]'): - # element = WebDriverWait(self.driver, 50).until(EC.element_to_be_clickable((By.XPATH, slider_xpath))) - element = WebDriverWait(self.driver, 50).until( - EC.element_to_be_clickable((By.CLASS_NAME, "geetest_slider_button"))) - ActionChains(self.driver).move_to_element(element).perform() # 鼠标移动到滑动框以显示图片 - js = 'document.querySelector("body > div.geetest_panel.geetest_wind ' \ - '> div.geetest_panel_box.geetest_no_logo.geetest_panelshowslide ' \ - '> div.geetest_panel_next > div > div.geetest_wrap > div.geetest_widget ' \ - '> div > a > div.geetest_canvas_img.geetest_absolute > canvas").' \ - 'style.display = "%s";' - self.driver.execute_script(js % "inline") - time.sleep(1) - true_image = self.get_img(self.fn) - self.driver.execute_script(js % "none") - return true_image - - def get_img(self, img_name, img_xpath=r'//*[@id="gc-box"]/div/div[1]/div[2]/div[1]/a[2]'): # 260*116 - fn = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'slider_screenshot.png') - - screen_shot = self.driver.save_screenshot(fn) - # image_element = self.driver.find_element_by_xpath(img_xpath) - image_element = self.driver.find_element_by_class_name(r'geetest_window') - left = image_element.location['x'] - top = image_element.location['y'] # selenium截图并获取验证图片location后将其截出保存 - right = image_element.location['x'] + image_element.size['width'] - bottom = image_element.location['y'] + image_element.size['height'] - image = Image.open(fn) - image = image.crop((left, top, right, bottom)) - image.save(img_name) - return image - - def analysis(self, true_image, knob_xpath=r'//*[@id="gc-box"]/div/div[3]/div[2]'): - fn = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'img2.png') - img1 = Image.open(self.fn) - # slider_element = self.driver.find_element_by_xpath(knob_xpath) - slider_element = self.driver.find_element_by_class_name("geetest_slider_button") - ActionChains(self.driver).click_and_hold(slider_element).perform() # 点击滑块后截取残缺图 - time.sleep(1) - img2 = self.get_img(fn) - img1_width, img1_height = img1.size - img2_width, img2_height = img2.size - left = 0 - flag = False - - for i in range(69, img1_width): # 遍历x>65的像素点(x<65是拼图块) - for j in range(0, img1_height): - if not self.is_pixel_equal(img1, img2, i, j): - left = i - flag = True - break - if flag: - break - if left >= 73: - left = left - 3 # 误差纠正 - else: - left = left - return left - - def is_pixel_equal(self, img1, img2, x, y): # 通过比较俩图片像素点RGB值差值判断是否为缺口 - pix1 = img1.load()[x, y] - pix2 = img2.load()[x, y] - if (abs(pix1[0] - pix2[0] < 60) and abs(pix1[1] - pix2[1] < 60) and abs(pix1[2] - pix2[2] < 60)): - return True - else: - return False - - def get_track(self, distance): - """ - 根据偏移量获取移动轨迹 - :param distance: 偏移量 - :return: 移动轨迹 - """ - # 移动轨迹 - track = [] - # 当前位移 - current = 0 - # 减速阈值 - mid = distance * 4 / 5 - # 计算间隔 - t = 0.2 - # 初速度 - v = 0 - - while current < distance: - if current < mid: - # 加速度为正2 - a = 2 - else: - # 加速度为负3 - a = -3 - # 初速度v0 - v0 = v - # 当前速度v = v0 + at - v = v0 + a * t - # 移动距离x = v0t + 1/2 * a * t^2 - move = v0 * t + 1 / 2 * a * t * t - # 当前位移 - current += move - # 加入轨迹 - track.append(round(move)) - # print(track) - # - # print(sum(track)) - track.append(distance - sum(track)) - # print(track) - # print(sum(track)) - return track - - def move_to_gap(self, slider, track): - """ - 拖动滑块到缺口处 - :param slider: 滑块 - :param track: 轨迹 - :return: - """ - ActionChains(self.driver).click_and_hold(slider).perform() - for x in track: - ActionChains(self.driver).move_by_offset(xoffset=x, yoffset=random.uniform(-5, 2)).perform() - time.sleep(0.5) - ActionChains(self.driver).release().perform() - - def crack(self): - true_image = self.get_true_image() - x_offset = self.analysis(true_image) - print(x_offset) - - track = self.get_track(x_offset) - knob_element = WebDriverWait(self.driver, 50).until( - EC.element_to_be_clickable((By.XPATH, r'/html/body/div[2]/div[2]/div[6]/div/div[1]/div[2]/div[2]'))) - self.move_to_gap(knob_element, track) - - # fn = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'result0.png') - # fn1 = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'result1.png') - # time.sleep(0.02) - # screen_shot = self.driver.save_screenshot(fn) - # time.sleep(2) - # screen_shot = self.driver.save_screenshot(fn1) diff --git a/config(demo).yaml b/config(demo).yaml index d688490..b9287c0 100644 --- a/config(demo).yaml +++ b/config(demo).yaml @@ -11,10 +11,10 @@ user: # 在填了cookies的情况下优先使用cookies上传,如需使用用 lines: AUTO # 单文件并发上传数,未达到带宽上限时增大此值可提高上传速度 threads: 3 -# 录像单文件大小限制,单位Byte,超过此大小分段下载,如需使用时间分段请注释此字段 +# 录像单文件大小限制,单位Byte,超过此大小分段下载 file_size: 2621440000 # 录像单文件时间限制,格式'00:00:00'(时分秒),超过此大小分段下载,如需使用大小分段请注释此字段 -segment_time: '00:50:00' +#segment_time: '00:50:00' streamers: 星际2INnoVation吕布卫星人族天梯第一视角: