diff --git a/Common.py b/Common.py deleted file mode 100644 index d2b448e..0000000 --- a/Common.py +++ /dev/null @@ -1,432 +0,0 @@ -import os -import queue -from datetime import datetime, timedelta -from glob import glob - -import psutil -from api import XiGuaLiveApi -import json -import threading -from bilibili import * - -# 默认设置 -config = { - # 录像的主播名 - "l_u": "永恒de草薙", - "b_u": "自己的B站账号", - "b_p": "自己的B站密码", - # 标题及预留时间位置 - "t_t": "【永恒de草薙直播录播】直播于 {}", - # 标签 - "tag": ["永恒de草薙", "三国", "三国战记", "直播录像", "录播", "怀旧", "街机"], - # 描述 - "des": "西瓜直播 https://live.ixigua.com/userlive/97621754276 \n自动投递\n原主播:永恒de草薙\n直播时间:晚上6点多到凌晨4点左右", - # 来源, 空则为自制 - "src": "", - # Log条数 - "l_c": 5, - # 错误Log条数 - "elc": 10, - "p_s": 2141000000, - "max": 75, - "exp": 1, - "dow": "echo 'clean'", - # 仅下载 - "dlO": True, - # 下播延迟投稿 - "dly": 30, - "enc": "ffmpeg -i {f} -c:v copy -c:a copy -f mp4 {t} -y" -} -doCleanTime = datetime.now() -loginTime = datetime.now() -_clean_flag = None -delay = datetime.now() -b = Bilibili() - -network = [{ - "currentTime": datetime.now(), - "out": { - "currentByte": psutil.net_io_counters().bytes_sent, - }, - "in": { - "currentByte": psutil.net_io_counters().bytes_recv, - } -}, { - "currentTime": datetime.now(), - "out": { - "currentByte": psutil.net_io_counters().bytes_sent, - }, - "in": { - "currentByte": psutil.net_io_counters().bytes_recv, - } -}] - - -def reloadConfig(): - global config - if(os.path.exists('config.json')): - _config_fp = open("config.json", "r", encoding="utf8") - _config = json.load(_config_fp) - config.update(_config) - _config_fp.close() - - -def resetDelay(): - global delay - delay = datetime.now() + timedelta(minutes=int(config['dly'])) - - -def doDelay(): - global delay, isBroadcasting, isEncode, isUpload - if isBroadcasting or isEncode or isUpload: - resetDelay() - return False - return datetime.now() > delay - - -def updateNetwork(): - global network - network.append({ - "currentTime": datetime.now(), - "out": { - "currentByte": psutil.net_io_counters().bytes_sent, - }, - "in": { - "currentByte": psutil.net_io_counters().bytes_recv, - } - }) - network = network[-3:] - - -def getTimeDelta(a, b): - sec = (a - b).seconds - ms = (a - b).microseconds - return sec+(ms/100000.0) - - -def _doClean(_force=False): - global doCleanTime, _clean_flag - _disk = psutil.disk_usage(".") - if _disk.percent > config["max"] or getTimeDelta(datetime.now(), doCleanTime) > config["exp"]*86400 or _force: - _clean_flag = True - doCleanTime = datetime.now() - appendOperation("执行配置的清理命令") - os.system(config["dow"]) - appendOperation("执行配置的清理命令完毕") - doCleanTime = datetime.now() - _clean_flag = False - - -def doClean(_force=False): - if _clean_flag: - return - p = threading.Thread(target=_doClean, args=(_force,)) - p.setDaemon(True) - p.start() - - -def getCurrentStatus(): - _disk = psutil.disk_usage(".") - _mem = psutil.virtual_memory() - _net = psutil.net_io_counters() - _delta= getTimeDelta(network[-1]["currentTime"], network[-2]["currentTime"]) - if 60 > _delta > 1: - _inSpeed = (network[-1]["in"]["currentByte"] - network[-2]["in"]["currentByte"]) / _delta - _outSpeed = (network[-1]["out"]["currentByte"] - network[-2]["out"]["currentByte"]) / _delta - else: - _outSpeed = (network[-1]["in"]["currentByte"] - network[-2]["in"]["currentByte"]) - _inSpeed = (network[-1]["out"]["currentByte"] - network[-2]["out"]["currentByte"]) - updateNetwork() - return { - "memTotal": parseSize(_mem.total), - "memUsed": parseSize(_mem.used), - "memUsage": _mem.percent, - "diskTotal": parseSize(_disk.total), - "diskUsed": parseSize(_disk.used), - "diskUsage": _disk.percent, - "cpu": psutil.cpu_percent(), - "outSpeed": parseSize(_outSpeed), - "inSpeed": parseSize(_inSpeed), - "doCleanTime": datetime.strftime(doCleanTime, dt_format), - "fileExpire": config["exp"], - } - - -dt_format = "%Y/%m/%d %H:%M:%S" -reloadConfig() -broadcaster = "" -streamUrl = "" -isBroadcasting = False -updateTime = "" - -forceNotDownload = False -forceNotBroadcasting = False -forceNotUpload = False -forceNotEncode = False -if config["dlO"] is True: - forceNotUpload = True - forceNotEncode = True -forceStartEncodeThread = False -forceStartUploadThread = False -isEncode = True -isUpload = True - -uploadQueue = queue.Queue() -encodeQueue = queue.Queue() - -uploadStatus = [] -downloadStatus = [] -encodeStatus = [] -errors = [] -operations = [] - - -def appendOperation(obj): - global operations - if isinstance(obj, dict): - if "datetime" not in obj: - obj["datetime"] = datetime.strftime(datetime.now(), dt_format) - operations.append(obj) - else: - operations.append({ - "datetime": datetime.strftime(datetime.now(), dt_format), - "message": str(obj) - }) - operations = operations[-config["elc"]:] - - -def parseSize(size): - K = size / 1024.0 - if K > 1000: - M = K / 1024.0 - if M > 1000: - return "{:.2f}GB".format(M / 1024.0) - else: - return "{:.2f}MB".format(M) - else: - return "{:.2f}KB".format(K) - - -def appendUploadStatus(obj): - global uploadStatus - if isinstance(obj, dict): - if "datetime" not in obj: - obj["datetime"] = datetime.strftime(datetime.now(), dt_format) - uploadStatus.append(obj) - else: - uploadStatus.append({ - "datetime": datetime.strftime(datetime.now(), dt_format), - "message": str(obj) - }) - uploadStatus = uploadStatus[-config["l_c"]:] - - -def modifyLastUploadStatus(obj): - global uploadStatus - if isinstance(obj, dict): - if "datetime" not in obj: - obj["datetime"] = datetime.strftime(datetime.now(), dt_format) - uploadStatus[-1] = obj - else: - uploadStatus[-1]["message"] = str(obj) - uploadStatus[-1]["datetime"] = datetime.strftime(datetime.now(), dt_format) - - -def appendEncodeStatus(obj): - global encodeStatus - if isinstance(obj, dict): - if "datetime" not in obj: - obj["datetime"] = datetime.strftime(datetime.now(), dt_format) - encodeStatus.append(obj) - else: - encodeStatus.append({ - "datetime": datetime.strftime(datetime.now(), dt_format), - "message": str(obj) - }) - encodeStatus = encodeStatus[-config["l_c"]:] - - -def modifyLastEncodeStatus(obj): - global encodeStatus - if isinstance(obj, dict): - if "datetime" not in obj: - obj["datetime"] = datetime.strftime(datetime.now(), dt_format) - encodeStatus[-1] = obj - else: - encodeStatus[-1]["message"] = str(obj) - encodeStatus[-1]["datetime"] = datetime.strftime(datetime.now(), dt_format) - - -def appendDownloadStatus(obj): - global downloadStatus - if isinstance(obj, dict): - if "datetime" not in obj: - obj["datetime"] = datetime.strftime(datetime.now(), dt_format) - downloadStatus.append(obj) - else: - downloadStatus.append({ - "datetime": datetime.strftime(datetime.now(), dt_format), - "message": str(obj) - }) - downloadStatus = downloadStatus[-config["l_c"]:] - - -def modifyLastDownloadStatus(obj): - global downloadStatus - if isinstance(obj, dict): - if "datetime" not in obj: - obj["datetime"] = datetime.strftime(datetime.now(), dt_format) - downloadStatus[-1] = obj - else: - downloadStatus[-1]["message"] = str(obj) - downloadStatus[-1]["datetime"] = datetime.strftime(datetime.now(), dt_format) - - -def appendError(obj): - global errors - if isinstance(obj, dict): - if "datetime" not in obj: - obj["datetime"] = datetime.strftime(datetime.now(), dt_format) - errors.append(obj) - else: - errors.append({ - "datetime": datetime.strftime(datetime.now(), dt_format), - "message": str(obj) - }) - errors = errors[-config["elc"]:] - - -def loginBilibili(force=False): - if config["dlO"] is False or forceNotUpload is False: - global loginTime - if not force and getTimeDelta(datetime.now(), loginTime) < 86400 * 5: - return False - res = b.login(config["b_u"], config["b_p"]) - loginTime = datetime.now() - appendOperation("登陆账号,结果为:[{}]".format(res)) - return res - else: - appendOperation("设置了不上传,所以不会登陆") - - -class downloader(XiGuaLiveApi): - playlist = None - - - def updRoomInfo(self, force=False): - doClean() - super(downloader, self).updRoomInfo(force) - - def _updateUserOnly(self): - global broadcaster, isBroadcasting, updateTime - super(downloader, self)._updateUserOnly() - updateTime = datetime.strftime(datetime.now(), dt_format) - broadcaster = self.roomLiver - isBroadcasting = self.isLive - if self.isLive: - self.updPlayList() - else: - resetDelay() - self.playlist = False - - def updPlayList(self): - global streamUrl - if self.isLive and "stream_url" in self._rawRoomInfo: - self.playlist = self._rawRoomInfo["stream_url"]["flv_pull_url"] - self.playlist = self.playlist.replace("_uhd", "").replace("_sd", "").replace("_ld", "") - streamUrl = self.playlist - else: - streamUrl = None - self.playlist = None - - def onLike(self, user): - pass - - def onAd(self, i): - pass - - def onChat(self, chat): - pass - - def onEnter(self, msg): - pass - - def onJoin(self, user): - pass - - def onLeave(self, json): - self.updRoomInfo() - - def onMessage(self, msg): - pass - - def onPresent(self, gift): - pass - - def onPresentEnd(self, gift): - pass - - def onSubscribe(self, user): - pass - - -api = downloader(config["l_u"]) - - -def refreshDownloader(): - global api - api = downloader(config["l_u"]) - - -def uploadVideo(name): - global isUpload - if not os.path.exists(name): - Common.appendError("Upload File Not Exist {}".format(name)) - return - isUpload = True - loginBilibili() - doClean() - if forceNotUpload is False: - b.preUpload(VideoPart(name, os.path.basename(name))) - else: - appendUploadStatus("设置了不上传,所以[{}]不会上传了".format(name)) - isUpload = False - if not Common.forceNotEncode: - os.remove(name) - - -def publishVideo(date): - global isUpload - if forceNotUpload is False: - b.finishUpload(config["t_t"].format(date), 17, config["tag"], config["des"], - source=config["src"], no_reprint=0) - b.clear() - else: - appendUploadStatus("设置了不上传,所以[{}]的录播不会上传了".format(date)) - isUpload = False - - -def encodeVideo(name): - if forceNotEncode: - appendEncodeStatus("设置了不编码,所以[{}]不会编码".format(name)) - return False - if not os.path.exists(name): - appendEncodeStatus("文件[{}]不存在".format(name)) - return False - if os.path.getsize(name) < 8 * 1024 * 1024: - appendEncodeStatus("Encoded File >{}< is too small, will ignore it".format(name)) - return False - global isEncode - isEncode=True - appendEncodeStatus("Encoding >{}< Start".format(name)) - _new_name = os.path.splitext(name)[0]+".mp4" - _code = os.system(config["enc"].format(f=name, t=_new_name)) - if _code != 0: - Common.appendError("Encode {} with Non-Zero Return.".format(name)) - return False - Common.modifyLastEncodeStatus("Encode >{}< Finished".format(name)) - uploadQueue.put(_new_name) - isEncode=False - - -loginBilibili(True) diff --git a/README.md b/README.md index 5294775..8dae1fc 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # XiguaLiveDanmakuHelper -### 因西瓜直播弹幕接口加密了,所以该项目会尽量保证录播(获取房间信息及搜索用户信息)可正常使用,其他的接口看西瓜视频的心情了 +### 因西瓜直播弹幕接口换成了ProtoBuf,没有对应proto文件没有办法进行解析 ### 西瓜直播弹幕助手--控制台版 @@ -8,16 +8,7 @@ ### 西瓜直播弹幕接口```api.py``` -> - 西瓜直播的弹幕接口已经加密,有大佬可以去尝试解析一下 - -### 西瓜直播弹幕助手--录播端```WebMain.py``` - -> - 能够自动进行ffmpeg转码 -> - 转码后自动上传至B站 -> - 顺便还能自己清理录播的文件(移动到一个位置,执行shell命令,上传百度云) -> - 把录像文件分一定大小保存(B站有限制,但是不知道是多少) -> - 少部分错误包容机制 -> - 有一个简单的WEB页面,及简单的控制接口 +> - 西瓜直播的弹幕接口已经换成了ProtoBuf,有大佬可以去尝试解析一下 ### 西瓜直播弹幕助手--礼物端```WinMain.py``` diff --git a/WebMain.py b/WebMain.py deleted file mode 100644 index 7a502c0..0000000 --- a/WebMain.py +++ /dev/null @@ -1,268 +0,0 @@ -import os -from glob import glob -from time import sleep -from flask_cors import CORS -from flask import Flask, jsonify, request, redirect, render_template, Response -import Common -import threading -from liveDownloader import run as RUN - -app = Flask(__name__) -app.config['JSON_AS_ASCII'] = False -CORS(app, supports_credentials=True) - - -@app.route("/") -def index(): - return render_template("index.html") - - -@app.route("/config", methods=["GET"]) -def readConfig(): - config = Common.config.copy() - config.pop("b_p") - config.pop("mv") - return jsonify(config) - - -@app.route("/config", methods=["POST"]) -def writeConfig(): - # TODO : 完善 - Common.appendOperation("更新配置") - Common.reloadConfig() - return jsonify({"message":"ok","code":200,"status":0,"data":request.form}) - - -@app.route("/force/not/upload", methods=["POST"]) -def toggleForceNotUpload(): - Common.forceNotUpload = not Common.forceNotUpload - Common.appendOperation("将强制不上传的值改为:{}".format(Common.forceNotUpload)) - return jsonify({"message":"ok","code":200,"status":0,"data":{ - "forceNotUpload": Common.forceNotUpload, - }}) - - -@app.route("/force/not/encode", methods=["POST"]) -def toggleForceNotEncode(): - Common.forceNotEncode = not Common.forceNotEncode - Common.appendOperation("将强制不编码的值改为:{}".format(Common.forceNotEncode)) - return jsonify({"message":"ok","code":200,"status":0,"data":{ - "forceNotEncode": Common.forceNotEncode, - }}) - - -@app.route("/force/not/download", methods=["POST"]) -def toggleForceNotDownload(): - Common.forceNotDownload = not Common.forceNotDownload - Common.appendOperation("将强制不下载的值改为:{}".format(Common.forceNotDownload)) - return jsonify({"message":"ok","code":200,"status":0,"data":{ - "forceNotDownload": Common.forceNotDownload, - }}) - - -@app.route("/force/not/broadcast", methods=["POST"]) -def toggleForceNotBroadcast(): - Common.forceNotBroadcasting = not Common.forceNotBroadcasting - return jsonify({"message":"ok","code":200,"status":0,"data":{ - "forceNotBroadcasting": Common.forceNotBroadcasting, - }}) - - -@app.route("/force/start/encode", methods=["POST"]) -def toggleForceStartEncodeThread(): - Common.forceStartEncodeThread = True - Common.appendOperation("强制运行编码线程") - return jsonify({"message":"ok","code":200,"status":0,"data":{ - }}) - - -@app.route("/force/start/upload", methods=["POST"]) -def toggleForceStartUploadThread(): - Common.forceStartUploadThread = True - Common.appendOperation("强制运行上传线程") - return jsonify({"message":"ok","code":200,"status":0,"data":{ - }}) - - -@app.route("/force/start/clean", methods=["POST"]) -def startForceCleanDisk(): - Common.doClean(True) - Common.appendOperation("强制执行清理程序") - return jsonify({"message":"ok","code":200,"status":0,"data":{ - }}) - - -@app.route("/encode/insert", methods=["POST"]) -def insertEncode(): - if "filename" in request.form and os.path.exists(request.form["filename"]): - Common.appendOperation("添加编码文件:{}".format(request.form["filename"])) - Common.encodeQueue.put(request.form["filename"]) - return jsonify({"message":"ok","code":200,"status":0}) - else: - return jsonify({"message":"no filename specific","code":400,"status":1}) - - -@app.route("/upload/insert", methods=["POST"]) -def insertUpload(): - if "filename" in request.form and os.path.exists(request.form["filename"]): - Common.appendOperation("添加上传文件:{}".format(request.form["filename"])) - Common.uploadQueue.put(request.form["filename"]) - return jsonify({"message":"ok","code":200,"status":0}) - else: - return jsonify({"message":"no filename specific","code":400,"status":1}) - - -@app.route("/upload/finish", methods=["POST"]) -def finishUpload(): - Common.appendOperation("设置当前已完成上传") - Common.uploadQueue.put(True) - return jsonify({"message":"ok","code":200,"status":0}) - - -@app.route("/stats", methods=["GET"]) -def getAllStats(): - return jsonify({"message":"ok","code":200,"status":0,"data":{ - "download":Common.downloadStatus, - "encode": Common.encodeStatus, - "encodeQueueSize": Common.encodeQueue.qsize(), - "upload": Common.uploadStatus, - "uploadQueueSize": Common.uploadQueue.qsize(), - "error": Common.errors, - "operation": Common.operations, - "broadcast": { - "broadcaster": Common.broadcaster.__str__(), - "isBroadcasting": Common.isBroadcasting, - "streamUrl": Common.streamUrl, - "updateTime": Common.updateTime, - "delayTime": Common.delay - }, - "config": { - "forceNotBroadcasting": Common.forceNotBroadcasting, - "forceNotDownload": Common.forceNotDownload, - "forceNotUpload": Common.forceNotUpload, - "forceNotEncode": Common.forceNotEncode, - "downloadOnly": Common.config['dlO'], - }, - }}) - - -@app.route("/stats/device", methods=["GET"]) -def getDeviceStatus(): - return jsonify({"message":"ok","code":200,"status":0,"data":{ - "status": Common.getCurrentStatus(), - }}) - - -@app.route("/stats/broadcast", methods=["GET"]) -def getBroadcastStats(): - return jsonify({"message":"ok","code":200,"status":0,"data":{ - "broadcast": { - "broadcaster": Common.broadcaster.__str__(), - "isBroadcasting": Common.isBroadcasting, - "streamUrl": Common.streamUrl, - "updateTime": Common.updateTime, - "delayTime": Common.delay - } - }}) - - -@app.route("/stats/config", methods=["GET"]) -def getConfigStats(): - return jsonify({"message":"ok","code":200,"status":0,"data":{ - "config": { - "forceNotBroadcasting": Common.forceNotBroadcasting, - "forceNotDownload": Common.forceNotDownload, - "forceNotUpload": Common.forceNotUpload, - "forceNotEncode": Common.forceNotEncode, - "downloadOnly": Common.config['dlO'], - } - }}) - - -@app.route("/stats/download", methods=["GET"]) -def getDownloadStats(): - return jsonify({"message":"ok","code":200,"status":0,"data":{ - "download":Common.downloadStatus, - }}) - - -@app.route("/stats/encode", methods=["GET"]) -def getEncodeStats(): - return jsonify({"message":"ok","code":200,"status":0,"data":{ - "encode": Common.encodeStatus, - "encodeQueueSize": Common.encodeQueue.qsize(), - }}) - - -@app.route("/stats/upload", methods=["GET"]) -def getUploadStats(): - return jsonify({"message":"ok","code":200,"status":0,"data":{ - "upload": Common.uploadStatus, - "uploadQueueSize": Common.uploadQueue.qsize(), - }}) - - -@app.route("/account/reLogin", methods=["POST"]) -def accountRelogin(): - res = Common.loginBilibili(True) - return jsonify({"message":"ok","code":200,"status":0,"data":{"result":res}}) - - -@app.route("/files/", methods=["GET"]) -def fileIndex(): - a = [] - for i in (glob("*.mp4") + glob("*.flv")): - a.append({ - "name": i, - "size": Common.parseSize(os.path.getsize(i)) - }) - return render_template("files.html",files=a) - - -@app.route("/files/download/", methods=["GET"]) -def fileDownload(path): - def generate(file, offset=0): - with open(file, "rb") as f: - f.seek(offset) - for row in f: - yield row - if os.path.exists(path): - if "RANGE" in request.headers: - offset = int(request.headers["RANGE"].replace("=","-").split("-")[1].strip()) - code = 206 - else: - offset = 0 - code = 200 - return Response(generate(path, offset), - status=code, - mimetype='application/octet-stream', - headers={ - "Content-Length": os.path.getsize(path), - "Content-Range": "bytes {}-{}/{}".format(offset,os.path.getsize(path)-1,os.path.getsize(path)), - "Accept-Ranges": "bytes", - "Range": "bytes", - }) - else: - return Response(status=404) - - -def SubThread(): - t = threading.Thread(target=RUN, args=()) - t.setDaemon(True) - t.start() - while True: - if t.is_alive(): - sleep(240) - else: - t = threading.Thread(target=RUN, args=()) - t.setDaemon(True) - t.start() - - -if not app.debug: - p = threading.Thread(target=SubThread) - p.setDaemon(True) - p.start() - -if __name__ == "__main__": - app.run() diff --git a/bilibili.py b/bilibili.py deleted file mode 100644 index 8ed3553..0000000 --- a/bilibili.py +++ /dev/null @@ -1,524 +0,0 @@ -# coding=utf-8 - -import os -import re -import json as JSON -from datetime import datetime -from time import sleep -import Common -import rsa -import math -import base64 -import hashlib -import requests -from urllib import parse - - -class VideoPart: - def __init__(self, path, title='', desc=''): - self.path = path - self.title = title - self.desc = desc - - -class Bilibili: - def __init__(self, cookie=None): - self.files = [] - self.videos = [] - self.session = requests.session() - self.session.keep_alive = False - if cookie: - self.session.headers["cookie"] = cookie - self.csrf = re.search('bili_jct=(.*?);', cookie).group(1) - self.mid = re.search('DedeUserID=(.*?);', cookie).group(1) - self.session.headers['Accept'] = 'application/json, text/javascript, */*; q=0.01' - self.session.headers['Referer'] = 'https://space.bilibili.com/{mid}/#!/'.format(mid=self.mid) - # session.headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36' - # session.headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8' - - def login(self, user, pwd): - """ - - :param user: username - :type user: str - :param pwd: password - :type pwd: str - :return: if success return True - else return msg json - """ - APPKEY = '4409e2ce8ffd12b8' - ACTIONKEY = 'appkey' - BUILD = 101800 - DEVICE = 'android' - MOBI_APP = 'android' - PLATFORM = 'android' - APPSECRET = '59b43e04ad6965f34319062b478f83dd' - - def md5(s): - h = hashlib.md5() - h.update(s.encode('utf-8')) - return h.hexdigest() - - def sign(s): - """ - - :return: return sign - """ - return md5(s + APPSECRET) - - def signed_body(body): - """ - - :return: body which be added sign - """ - if isinstance(body, str): - return body + '&sign=' + sign(body) - elif isinstance(body, dict): - ls = [] - for k, v in body.items(): - ls.append(k + '=' + v) - body['sign'] = sign('&'.join(ls)) - return body - - def getkey(): - """ - - :return: hash, key - """ - r = self.session.post( - 'https://passport.bilibili.com/api/oauth2/getKey', - signed_body({'appkey': APPKEY}), - ) - # {"ts":1544152439,"code":0,"data":{"hash":"99c7573759582e0b","key":"-----BEGIN PUBLIC----- -----END PUBLIC KEY-----\n"}} - json = r.json() - data = json['data'] - return data['hash'], data['key'] - - def cnn_captcha(img): - url = "http://47.95.255.188:5000/code" - data = {"image": img} - r = requests.post(url, data=data) - return r.text - - self.session.headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8' - h, k = getkey() - pwd = base64.b64encode( - rsa.encrypt( - (h + pwd).encode('utf-8'), - rsa.PublicKey.load_pkcs1_openssl_pem(k.encode()) - ) - ) - user = parse.quote_plus(user) - pwd = parse.quote_plus(pwd) - - r = self.session.post( - 'https://passport.bilibili.com/api/v2/oauth2/login', - signed_body('appkey={appkey}&password={password}&username={username}' - .format(appkey=APPKEY, username=user, password=pwd)) - ) - try: - json = r.json() - except: - return r.text - - if json['code'] == -105: - # need captcha - self.session.headers['cookie'] = 'sid=xxxxxxxx' - r = self.session.get('https://passport.bilibili.com/captcha') - captcha = cnn_captcha(base64.b64encode(r.content)) - r = self.session.post( - 'https://passport.bilibili.com/api/v2/oauth2/login', - signed_body('actionKey={actionKey}&appkey={appkey}&build={build}&captcha={captcha}&device={device}' - '&mobi_app={mobi_app}&password={password}&platform={platform}&username={username}' - .format(actionKey=ACTIONKEY, - appkey=APPKEY, - build=BUILD, - captcha=captcha, - device=DEVICE, - mobi_app=MOBI_APP, - password=pwd, - platform=PLATFORM, - username=user)), - ) - json = r.json() - - if json['code'] is not 0: - return r.text - - ls = [] - for item in json['data']['cookie_info']['cookies']: - ls.append(item['name'] + '=' + item['value']) - cookie = '; '.join(ls) - self.session.headers["cookie"] = cookie - - self.csrf = re.search('bili_jct=(.*?);', cookie).group(1) - self.mid = re.search('DedeUserID=(.*?);', cookie).group(1) - self.session.headers['Accept'] = 'application/json, text/javascript, */*; q=0.01' - self.session.headers['Referer'] = 'https://space.bilibili.com/{mid}/#!/'.format(mid=self.mid) - - return True - - def upload(self, - parts, - title, - tid, - tag, - desc, - source='', - cover='', - no_reprint=1, - ): - """ - - :param parts: e.g. VideoPart('part path', 'part title', 'part desc'), or [VideoPart(...), VideoPart(...)] - :type parts: VideoPart or list - :param title: video's title - :type title: str - :param tid: video type, see: https://member.bilibili.com/x/web/archive/pre - or https://github.com/uupers/BiliSpider/wiki/%E8%A7%86%E9%A2%91%E5%88%86%E5%8C%BA%E5%AF%B9%E5%BA%94%E8%A1%A8 - :type tid: int - :param tag: video's tag - :type tag: list - :param desc: video's description - :type desc: str - :param source: (optional) 转载地址 - :type source: str - :param cover: (optional) cover's URL, use method *cover_up* to get - :type cover: str - :param no_reprint: (optional) 0=可以转载, 1=禁止转载(default) - :type no_reprint: int - """ - self.preUpload(parts) - self.finishUpload(title, tid, tag, desc, source, cover, no_reprint) - self.clear() - - def preUpload(self, parts): - """ - :param parts: e.g. VideoPart('part path', 'part title', 'part desc'), or [VideoPart(...), VideoPart(...)] - :type parts: VideoPart or list - """ - self.session.headers['Content-Type'] = 'application/json; charset=utf-8' - if not isinstance(parts, list): - parts = [parts] - - for part in parts: - filepath = part.path - filename = os.path.basename(filepath) - filesize = os.path.getsize(filepath) - Common.appendUploadStatus("Upload >{}< Started".format(filepath)) - self.files.append(part) - r = self.session.get('https://member.bilibili.com/preupload?' - 'os=upos&upcdn=ws&name={name}&size={size}&r=upos&profile=ugcupos%2Fyb&ssl=0' - .format(name=filename, size=filesize)) - """return example - { - "upos_uri": "upos://ugc/i181012ws18x52mti3gg0h33chn3tyhp.mp4", - "biz_id": 58993125, - "endpoint": "//upos-hz-upcdnws.acgvideo.com", - "endpoints": [ - "//upos-hz-upcdnws.acgvideo.com", - "//upos-hz-upcdntx.acgvideo.com" - ], - "chunk_retry_delay": 3, - "chunk_retry": 200, - "chunk_size": 4194304, - "threads": 2, - "timeout": 900, - "auth": "os=upos&cdn=upcdnws&uid=&net_state=4&device=&build=&os_version=&ak=×tamp=&sign=", - "OK": 1 - } - """ - json = r.json() - upos_uri = json['upos_uri'] - endpoint = json['endpoint'] - auth = json['auth'] - biz_id = json['biz_id'] - chunk_size = json['chunk_size'] - self.session.headers['X-Upos-Auth'] = auth # add auth header - r = self.session.post( - 'https:{}/{}?uploads&output=json'.format(endpoint, upos_uri.replace('upos://', ''))) - # {"upload_id":"72eb747b9650b8c7995fdb0efbdc2bb6","key":"\/i181012ws2wg1tb7tjzswk2voxrwlk1u.mp4","OK":1,"bucket":"ugc"} - json = r.json() - upload_id = json['upload_id'] - with open(filepath, 'rb') as f: - chunks_num = math.ceil(filesize / chunk_size) - chunks_index = 0 - chunks_data = f.read(chunk_size) - Common.modifyLastUploadStatus("Uploading >{}< @ {:.2f}%".format(filepath, 100.0 * chunks_index / chunks_num)) - while True: - _d = datetime.now() - if not chunks_data: - break - r = self.session.put('https:{endpoint}/{upos_uri}?' - 'partNumber={part_number}&uploadId={upload_id}&chunk={chunk}&chunks={chunks}&size={size}&start={start}&end={end}&total={total}' - .format(endpoint=endpoint, - upos_uri=upos_uri.replace('upos://', ''), - part_number=chunks_index + 1, # starts with 1 - upload_id=upload_id, - chunk=chunks_index, - chunks=chunks_num, - size=len(chunks_data), - start=chunks_index * chunk_size, - end=chunks_index * chunk_size + len(chunks_data), - total=filesize, - ), - chunks_data, - ) - if r.status_code != 200: - continue - chunks_data = f.read(chunk_size) - chunks_index += 1 # start with 0 - Common.modifyLastUploadStatus("Uploading >{}< @ {:.2f}%".format(filepath, 100.0*chunks_index/chunks_num)) - if (datetime.now()-_d).seconds < 2: - sleep(1) - - # NOT DELETE! Refer to https://github.com/comwrg/bilibiliupload/issues/15#issuecomment-424379769 - self.session.post('https:{endpoint}/{upos_uri}?' - 'output=json&name={name}&profile=ugcupos%2Fyb&uploadId={upload_id}&biz_id={biz_id}' - .format(endpoint=endpoint, - upos_uri=upos_uri.replace('upos://', ''), - name=filename, - upload_id=upload_id, - biz_id=biz_id, - ), - {"parts": [{"partNumber": i, "eTag": "etag"} for i in range(1, chunks_num + 1)]}, - ) - self.videos.append({'filename': upos_uri.replace('upos://ugc/', '').split('.')[0], - 'title': part.title, - 'desc': part.desc}) - Common.modifyLastUploadStatus("Upload >{}< Finished".format(filepath)) - __f = open("uploaded.json","w") - JSON.dump(self.videos, __f) - - - def finishUpload(self, - title, - tid, - tag, - desc, - source='', - cover='', - no_reprint=1, - ): - """ - :param title: video's title - :type title: str - :param tid: video type, see: https://member.bilibili.com/x/web/archive/pre - or https://github.com/uupers/BiliSpider/wiki/%E8%A7%86%E9%A2%91%E5%88%86%E5%8C%BA%E5%AF%B9%E5%BA%94%E8%A1%A8 - :type tid: int - :param tag: video's tag - :type tag: list - :param desc: video's description - :type desc: str - :param source: (optional) 转载地址 - :type source: str - :param cover: (optional) cover's URL, use method *cover_up* to get - :type cover: str - :param no_reprint: (optional) 0=可以转载, 1=禁止转载(default) - :type no_reprint: int - """ - if len(self.videos) == 0: - return - Common.appendUploadStatus("[{}]投稿中,请稍后".format(title)) - self.session.headers['Content-Type'] = 'application/json; charset=utf-8' - copyright = 2 if source else 1 - r = self.session.post('https://member.bilibili.com/x/vu/web/add?csrf=' + self.csrf, - json={ - "copyright": copyright, - "source": source, - "title": title, - "tid": tid, - "tag": ','.join(tag), - "no_reprint": no_reprint, - "desc": desc, - "cover": cover, - "mission_id": 0, - "order_id": 0, - "videos": self.videos} - ) - Common.modifyLastUploadStatus("[{}] Published | Result : {}".format(title, r.text)) - - def reloadFromPrevious(self): - if os.path.exists("uploaded.json"): - __f = open("uploaded.json", "r") - try: - self.videos = JSON.load(__f) - Common.appendUploadStatus("RELOAD SUCCESS") - except: - Common.appendUploadStatus("RELOAD Failed") - self.videos = [] - __f.close() - os.remove("uploaded.json") - else: - Common.appendUploadStatus("RELOAD Failed") - self.videos = [] - - def clear(self): - self.files.clear() - self.videos.clear() - if(os.path.exists("uploaded.json")): - os.remove("uploaded.json") - - def appendUpload(self, - aid, - parts, - title="", - tid="", - tag="", - desc="", - source='', - cover='', - no_reprint=1, - ): - """ - :param aid: just aid - :type aid: int - :param parts: e.g. VideoPart('part path', 'part title', 'part desc'), or [VideoPart(...), VideoPart(...)] - :type parts: VideoPart or list - :param title: video's title - :type title: str - :param tid: video type, see: https://member.bilibili.com/x/web/archive/pre - or https://github.com/uupers/BiliSpider/wiki/%E8%A7%86%E9%A2%91%E5%88%86%E5%8C%BA%E5%AF%B9%E5%BA%94%E8%A1%A8 - :type tid: int - :param tag: video's tag - :type tag: list - :param desc: video's description - :type desc: str - :param source: (optional) 转载地址 - :type source: str - :param cover: (optional) cover's URL, use method *cover_up* to get - :type cover: str - :param no_reprint: (optional) 0=可以转载, 1=禁止转载(default) - :type no_reprint: int - """ - self.session.headers['Content-Type'] = 'application/json; charset=utf-8' - p = self.session.get("https://member.bilibili.com/x/web/archive/view?aid={}&history=".format(aid)) - j = p.json() - if len(self.videos) == 0: - for i in j['data']['videos']: - self.videos.append({'filename': i['filename'], - 'title': i["title"], - 'desc': i["desc"]}) - if (title == ""): title = j["data"]["archive"]['title'] - if (tag == ""): tag = j["data"]["archive"]['tag'] - if (no_reprint == ""): no_reprint = j["data"]["archive"]['no_reprint'] - if (desc == ""): desc = j["data"]["archive"]['desc'] - if (source == ""): source = j["data"]["archive"]['source'] - if (tid == ""): tid = j["data"]["archive"]['tid'] - self.preUpload(parts) - self.editUpload(aid, title, tid, tag, desc, source, cover, no_reprint) - - def editUpload(self, - aid, - title, - tid, - tag, - desc, - source='', - cover='', - no_reprint=1, - ): - """ - :param aid: just aid - :type aid: int - :param parts: e.g. VideoPart('part path', 'part title', 'part desc'), or [VideoPart(...), VideoPart(...)] - :type parts: VideoPart or list - :param title: video's title - :type title: str - :param tid: video type, see: https://member.bilibili.com/x/web/archive/pre - or https://github.com/uupers/BiliSpider/wiki/%E8%A7%86%E9%A2%91%E5%88%86%E5%8C%BA%E5%AF%B9%E5%BA%94%E8%A1%A8 - :type tid: int - :param tag: video's tag - :type tag: list - :param desc: video's description - :type desc: str - :param source: (optional) 转载地址 - :type source: str - :param cover: (optional) cover's URL, use method *cover_up* to get - :type cover: str - :param no_reprint: (optional) 0=可以转载, 1=禁止转载(default) - :type no_reprint: int - """ - copyright = 2 if source else 1 - r = self.session.post('https://member.bilibili.com/x/vu/web/edit?csrf=' + self.csrf, - json={ - "aid": aid, - "copyright": copyright, - "source": source, - "title": title, - "tid": tid, - "tag": ','.join(tag), - "no_reprint": no_reprint, - "desc": desc, - "cover": cover, - "mission_id": 0, - "order_id": 0, - "videos": self.videos} - ) - print(r.text) - - def addChannel(self, name, intro=''): - """ - - :param name: channel's name - :type name: str - :param intro: channel's introduction - :type intro: str - """ - r = self.session.post( - url='https://space.bilibili.com/ajax/channel/addChannel', - data={ - 'name': name, - 'intro': intro, - 'aids': '', - 'csrf': self.csrf, - }, - # name=123&intro=123&aids=&csrf=565d7ed17cef2cc8ad054210c4e64324&_=1497077610768 - - ) - # return - # {"status":true,"data":{"cid":"15812"}} - print(r.json()) - - def channel_addVideo(self, cid, aids): - """ - - :param cid: channel's id - :type cid: int - :param aids: videos' id - :type aids: list - """ - - r = self.session.post( - url='https://space.bilibili.com/ajax/channel/addVideo', - data={ - 'aids': '%2C'.join(aids), - 'cid': cid, - 'csrf': self.csrf - } - # aids=9953555%2C9872953&cid=15814&csrf=565d7ed17cef2cc8ad054210c4e64324&_=1497079332679 - ) - print(r.json()) - - def cover_up(self, img): - """ - - :param img: img path or stream - :type img: str or BufferedReader - :return: img URL - """ - - if isinstance(img, str): - f = open(img, 'rb') - else: - f = img - r = self.session.post( - url='https://member.bilibili.com/x/vu/web/cover/up', - data={ - 'cover': b'data:image/jpeg;base64,' + (base64.b64encode(f.read())), - 'csrf': self.csrf, - } - ) - # print(r.text) - # {"code":0,"data":{"url":"http://i0.hdslb.com/bfs/archive/67db4a6eae398c309244e74f6e85ae8d813bd7c9.jpg"},"message":"","ttl":1} - return r.json()['data']['url'] diff --git a/liveDownloader.py b/liveDownloader.py deleted file mode 100644 index d568ce2..0000000 --- a/liveDownloader.py +++ /dev/null @@ -1,149 +0,0 @@ -import shutil -import sys -import time -from datetime import datetime -import threading -import Common -import os -import requests - - -def download(): - session = requests.session() - while Common.api.isLive and not Common.forceNotDownload: - if not Common.streamUrl: - Common.appendError("Download with No StreamUrl Specific") - break - path = datetime.strftime(datetime.now(), "%Y%m%d_%H%M.flv") - p = session.get(Common.streamUrl, stream=True, timeout=10) - if p.status_code != 200: - Common.appendDownloadStatus("Download with Response {}".format(p.status_code)) - Common.api.updRoomInfo(True) - break - Common.appendDownloadStatus("Download >{}< Start".format(path)) - f = open(path, "wb") - _size = 0 - try: - for t in p.iter_content(chunk_size=64 * 1024): - if Common.forceNotDownload: - Common.modifyLastDownloadStatus("Force Stop Download".format(path)) - return - f.write(t) - _size += len(t) - Common.modifyLastDownloadStatus( - "Downloading >{}< @ {:.2f}%".format(path, 100.0 * _size / Common.config["p_s"])) - if _size > Common.config["p_s"]: - Common.modifyLastDownloadStatus("Download >{}< Exceed MaxSize".format(path)) - break - Common.modifyLastDownloadStatus("Download >{}< Finished".format(path)) - except Exception as e: - Common.appendError("Download >{}< With Exception {}".format(path, e.__str__())) - f.close() - if os.path.getsize(path) < 1024 * 1024: - Common.modifyLastDownloadStatus("Downloaded File >{}< is too small, will ignore it".format(path)) - os.remove(path) - return False - Common.encodeQueue.put(path) - Common.api.updRoomInfo() - - -def encode(): - Common.appendEncodeStatus("Encode Daemon Starting") - while True: - i = Common.encodeQueue.get() - Common.encodeVideo(i) - - -def upload(): - date = datetime.strftime(datetime.now(), "%Y_%m_%d") - Common.appendUploadStatus("Upload Daemon Starting") - i = Common.uploadQueue.get() - while True: - if i is True: - Common.publishVideo(date) - break - try: - Common.uploadVideo(i) - except Exception as e: - Common.appendError(e.__str__()) - time.sleep(120) - continue - i = Common.uploadQueue.get() - Common.appendUploadStatus("Upload Daemon Quiting") - - -t = threading.Thread(target=download, args=()) -ut = threading.Thread(target=upload, args=()) -et = threading.Thread(target=encode, args=()) - - -def awakeEncode(): - global et - if et.is_alive(): - return True - et = threading.Thread(target=encode, args=()) - et.setDaemon(True) - et.start() - return False - - -def awakeDownload(): - global t - if t.is_alive(): - return True - t = threading.Thread(target=download, args=()) - t.setDaemon(True) - t.start() - Common.api.updRoomInfo() - return False - - -def awakeUpload(): - global ut - if ut.is_alive(): - return True - ut = threading.Thread(target=upload, args=()) - ut.setDaemon(True) - ut.start() - return False - - -def run(): - Common.refreshDownloader() - if not Common.api.isValidRoom: - Common.appendError("[{}]房间未找到".format(Common.config["l_u"])) - return - while True: - if Common.api.isLive and not Common.forceNotBroadcasting: - if not Common.forceNotDownload: - awakeDownload() - if not Common.forceNotUpload: - awakeUpload() - if not Common.forceNotEncode: - awakeEncode() - try: - Common.api.updRoomInfo() - except Exception as e: - Common.appendError(e.__str__()) - time.sleep(2) - continue - time.sleep(0.5) - else: - try: - Common.api.updRoomInfo() - except Exception as e: - Common.appendError(e.__str__()) - Common.refreshDownloader() - if not Common.api.roomLiver: - Common.refreshDownloader() - if Common.forceStartEncodeThread: - awakeEncode() - Common.forceStartEncodeThread = False - if Common.forceStartUploadThread: - awakeUpload() - Common.forceStartUploadThread = False - if Common.doDelay(): - Common.uploadQueue.put(True) - Common.isEncode = True - Common.isUpload = True - time.sleep(5) diff --git a/static/device.js b/static/device.js deleted file mode 100644 index 12ece79..0000000 --- a/static/device.js +++ /dev/null @@ -1,26 +0,0 @@ -function deviceUpdate(){ - $.ajax( - "/stats/device", - { - success: function (res){ - $("#memTotal").text(res.data.status.memTotal) - $("#memUsed").text(res.data.status.memUsed) - $("#memUsage").text(res.data.status.memUsage) - $("#diskTotal").text(res.data.status.diskTotal) - $("#diskUsed").text(res.data.status.diskUsed) - $("#diskUsage").text(res.data.status.diskUsage) - $("#cpu").text(res.data.status.cpu) - $("#memUsageP").val(res.data.status.memUsage) - $("#diskUsageP").val(res.data.status.diskUsage) - $("#cpuP").val(res.data.status.cpu) - $("#inSpeed").text(res.data.status.inSpeed) - $("#outSpeed").text(res.data.status.outSpeed) - $("#doCleanTime").text(res.data.status.doCleanTime) - $("#fileExpire").text(res.data.status.fileExpire) - } - } - ) -} - -deviceUpdate() -setInterval(deviceUpdate,2000) diff --git a/static/index.js b/static/index.js deleted file mode 100644 index 4ad9983..0000000 --- a/static/index.js +++ /dev/null @@ -1,59 +0,0 @@ -function taskUpdate(){ - $.ajax( - "/stats", - { - success: function (res){ - $("#broadcaster").text(res.data.broadcast.broadcaster) - $("#isBroadcasting").text(res.data.broadcast.isBroadcasting) - $("#streamUrl").text(res.data.broadcast.streamUrl) - $("#delayTime").text(res.data.broadcast.delayTime) - $("#forceNotBroadcasting").text(res.data.config.forceNotBroadcasting) - $("#forceNotDownload").text(res.data.config.forceNotDownload) - $("#forceNotUpload").text(res.data.config.forceNotUpload) - $("#forceNotEncode").text(res.data.config.forceNotEncode) - $("#downloadOnly").text(res.data.config.downloadOnly) - $("#updateTime").text(res.data.broadcast.updateTime) - $("#encodeQueueSize").text(res.data.encodeQueueSize) - $("#uploadQueueSize").text(res.data.uploadQueueSize) - $("#download").html(function(){ - var ret = "" - res.data.download.reverse().forEach(function(obj){ - ret += "" + obj.datetime + "" + obj.message + "" - }) - return "" + ret + "
" - }) - $("#encode").html(function(){ - var ret = "" - res.data.encode.reverse().forEach(function(obj){ - ret += "" + obj.datetime + "" + obj.message + "" - }) - return "" + ret + "
" - }) - $("#upload").html(function(){ - var ret = "" - res.data.upload.reverse().forEach(function(obj){ - ret += "" + obj.datetime + "" + obj.message + "" - }) - return "" + ret + "
" - }) - $("#error").html(function(){ - var ret = "" - res.data.error.reverse().forEach(function(obj){ - ret += "" + obj.datetime + "" + obj.message + "" - }) - return "" + ret + "
" - }) - $("#operation").html(function(){ - var ret = "" - res.data.operation.reverse().forEach(function(obj){ - ret += "" + obj.datetime + "" + obj.message + "" - }) - return "" + ret + "
" - }) - } - } - ) -} - -taskUpdate() -setInterval(taskUpdate,8000) diff --git a/templates/device.html b/templates/device.html deleted file mode 100644 index 8d878c7..0000000 --- a/templates/device.html +++ /dev/null @@ -1,30 +0,0 @@ -

机器状态

- - - - - - - - - - - - - - - - - - - - - - - - - - -
CPU使用率%
内存使用率/(%)
磁盘使用率/(%)
网络速率/s/s
文件清理清理天前的文件@
- - diff --git a/templates/files.html b/templates/files.html deleted file mode 100644 index 40221cb..0000000 --- a/templates/files.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - 文件 - {% include 'head.html' %} - - -
-

所有录像文件

-

部分录像文件已转移至百度云,请在这里下载 提取码: ddxt

- - - - - {%for i in files %} - - - - {% endfor %} -
文件名文件大小链接
{{i.name}}{{i.size}}下载文件
-
-

录播信息页

- {% include 'device.html' %} -
- - \ No newline at end of file diff --git a/templates/head.html b/templates/head.html deleted file mode 100644 index c9f310b..0000000 --- a/templates/head.html +++ /dev/null @@ -1,13 +0,0 @@ - - - diff --git a/templates/index.html b/templates/index.html deleted file mode 100644 index 351910a..0000000 --- a/templates/index.html +++ /dev/null @@ -1,86 +0,0 @@ - - - - 录播 - {% include 'head.html' %} - - -
-

基本信息

- - - - - - - - - - - - - - - - - - - - - -
主播名
是否正在直播
直播视频流地址
信息更新时间
延迟投稿时间
-
-

特殊设置

- - - - - - - - - - - - - - - - - - - - - -
是否设置强制认为不直播
是否设置强制不下载
是否设置强制不上传
是否设置强制不转码
是否设置为仅下载(不上传不转码)
-
-

当前状态

- - - - - - - - - - - - - - - - - - - - - -
下载日志
转码日志
队列
上传日志
队列
错误日志
操作日志
-
-

所有录播文件

- {% include 'device.html' %} -
- - - \ No newline at end of file