控制台端

This commit is contained in:
Jerry Yan 2019-12-31 15:45:48 +08:00
parent f598a5da05
commit 58991a8c43
11 changed files with 2 additions and 1624 deletions

432
Common.py
View File

@ -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)

View File

@ -1,6 +1,6 @@
# XiguaLiveDanmakuHelper
### 因西瓜直播弹幕接口加密了,所以该项目会尽量保证录播(获取房间信息及搜索用户信息)可正常使用,其他的接口看西瓜视频的心情了
### 因西瓜直播弹幕接口换成了ProtoBuf没有对应proto文件没有办法进行解析
### 西瓜直播弹幕助手--控制台版
@ -8,16 +8,7 @@
### 西瓜直播弹幕接口```api.py```
> - 西瓜直播的弹幕接口已经加密,有大佬可以去尝试解析一下
### 西瓜直播弹幕助手--录播端```WebMain.py```
> - 能够自动进行ffmpeg转码
> - 转码后自动上传至B站
> - 顺便还能自己清理录播的文件移动到一个位置执行shell命令上传百度云
> - 把录像文件分一定大小保存B站有限制但是不知道是多少
> - 少部分错误包容机制
> - 有一个简单的WEB页面及简单的控制接口
> - 西瓜直播的弹幕接口已经换成了ProtoBuf,有大佬可以去尝试解析一下
### 西瓜直播弹幕助手--礼物端```WinMain.py```

View File

@ -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/<path>", 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()

View File

@ -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<VideoPart>
: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<str>
: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<VideoPart>
"""
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<str>
: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<VideoPart>
: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<str>
: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<VideoPart>
: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<str>
: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<int>
"""
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']

View File

@ -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)

View File

@ -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)

View File

@ -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 += "<tr><td class='time'>" + obj.datetime + "</td><td>" + obj.message + "</td></tr>"
})
return "<table>" + ret + "</table>"
})
$("#encode").html(function(){
var ret = ""
res.data.encode.reverse().forEach(function(obj){
ret += "<tr><td class='time'>" + obj.datetime + "</td><td>" + obj.message + "</td></tr>"
})
return "<table>" + ret + "</table>"
})
$("#upload").html(function(){
var ret = ""
res.data.upload.reverse().forEach(function(obj){
ret += "<tr><td class='time'>" + obj.datetime + "</td><td>" + obj.message + "</td></tr>"
})
return "<table>" + ret + "</table>"
})
$("#error").html(function(){
var ret = ""
res.data.error.reverse().forEach(function(obj){
ret += "<tr><td class='time'>" + obj.datetime + "</td><td>" + obj.message + "</td></tr>"
})
return "<table>" + ret + "</table>"
})
$("#operation").html(function(){
var ret = ""
res.data.operation.reverse().forEach(function(obj){
ret += "<tr><td class='time'>" + obj.datetime + "</td><td>" + obj.message + "</td></tr>"
})
return "<table>" + ret + "</table>"
})
}
}
)
}
taskUpdate()
setInterval(taskUpdate,8000)

View File

@ -1,30 +0,0 @@
<h1>机器状态</h1>
<table>
<tr>
<td class='title'>CPU使用率</td>
<td><progress id="cpuP" max="100" value="0"></progress></td>
<td><span id="cpu"></span>%</td>
</tr>
<tr>
<td class='title'>内存使用率</td>
<td><progress id="memUsageP" max="100" value="0"></progress></td>
<td><span id="memUsed"></span>/<span id="memTotal"></span>(<span id="memUsage"></span>%)</td>
</tr>
<tr>
<td class='title'>磁盘使用率</td>
<td><progress id="diskUsageP" max="100" value="0"></progress></td>
<td><span id="diskUsed"></span>/<span id="diskTotal"></span>(<span id="diskUsage"></span>%)</td>
</tr>
<tr>
<td class='title'>网络速率</td>
<td><span id="inSpeed"></span>/s</td>
<td><span id="outSpeed"></span>/s</td>
</tr>
<tr>
<td class='title'>文件清理</td>
<td>清理<span id="fileExpire"></span>天前的文件</td>
<td>@ <span id="doCleanTime"></span></td>
</tr>
</table>
<script src="/static/device.js"></script>

View File

@ -1,26 +0,0 @@
<!DOCTYPE html>
<html lang="zh_CN">
<head>
<title>文件</title>
{% include 'head.html' %}
</head>
<body>
<div>
<h1>所有录像文件</h1>
<p>部分录像文件已转移至百度云,请在<a href="https://pan.baidu.com/s/1ECnwiHnsm-3dSXNJGWlR2g">这里</a>下载 提取码: ddxt</p>
<table>
<tr>
<td>文件名</td><td>文件大小</td><td>链接</td>
</tr>
{%for i in files %}
<tr>
<td>{{i.name}}</td><td>{{i.size}}</td><td><a href="/files/download/{{i.name}}">下载文件</a></td>
</tr>
{% endfor %}
</table>
<hr/>
<h3><a href="/">录播信息页</a></h3>
{% include 'device.html' %}
</div>
</body>
</html>

View File

@ -1,13 +0,0 @@
<meta charset="UTF-8">
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<style>
td{
border: solid 1px lightgray;
}
.title{
width: 6em;
}
.time{
width: 10em;
}
</style>

View File

@ -1,86 +0,0 @@
<!DOCTYPE html>
<html lang="zh_CN">
<head>
<title>录播</title>
{% include 'head.html' %}
</head>
<body>
<div>
<h1>基本信息</h1>
<table>
<tr>
<td>主播名</td>
<td><span id="broadcaster"></span></td>
</tr>
<tr>
<td>是否正在直播</td>
<td><span id="isBroadcasting"></span></td>
</tr>
<tr>
<td>直播视频流地址</td>
<td><span id="streamUrl"></span></td>
</tr>
<tr>
<td>信息更新时间</td>
<td><span id="updateTime"></span></td>
</tr>
<tr>
<td>延迟投稿时间</td>
<td><span id="delayTime"></span></td>
</tr>
</table>
<hr/>
<h1>特殊设置</h1>
<table>
<tr>
<td>是否设置强制认为不直播</td>
<td><span id="forceNotBroadcasting"></span></td>
</tr>
<tr>
<td>是否设置强制不下载</td>
<td><span id="forceNotDownload"></span></td>
</tr>
<tr>
<td>是否设置强制不上传</td>
<td><span id="forceNotUpload"></span></td>
</tr>
<tr>
<td>是否设置强制不转码</td>
<td><span id="forceNotEncode"></span></td>
</tr>
<tr>
<td>是否设置为仅下载(不上传不转码)</td>
<td><span id="downloadOnly"></span></td>
</tr>
</table>
<hr/>
<h1>当前状态</h1>
<table>
<tr>
<td class='title'>下载日志</td>
<td><span id="download"></span></td>
</tr>
<tr>
<td class='title'>转码日志<br>队列<span id="encodeQueueSize"></span></td>
<td><span id="encode"></span></td>
</tr>
<tr>
<td class='title'>上传日志<br>队列<span id="uploadQueueSize"></span></td>
<td><span id="upload"></span></td>
</tr>
<tr>
<td class='title'>错误日志</td>
<td><span id="error"></span></td>
</tr>
<tr>
<td class='title'>操作日志</td>
<td><span id="operation"></span></td>
</tr>
</table>
<hr/>
<h3><a href="/files/">所有录播文件</a></h3>
{% include 'device.html' %}
</div>
<script src="../static/index.js"></script>
</body>
</html>