Merge branch 'with_api'

# Conflicts:
#	README.md
This commit is contained in:
2019-05-14 20:41:29 +08:00
22 changed files with 1175 additions and 176 deletions

322
Common.py Normal file
View File

@ -0,0 +1,322 @@
import os
import queue
from datetime import datetime
from glob import glob
import psutil
from api import XiGuaLiveApi
import json
import threading
from bypy import ByPy
_config_fp = open("config.json", "r", encoding="utf8")
config = json.load(_config_fp)
_config_fp.close()
bypy = ByPy()
doCleanTime = datetime.now()
_clean_flag = None
network = {
"currentTime": datetime.now(),
"out": {
"currentByte": psutil.net_io_counters().bytes_sent,
},
"in": {
"currentByte": psutil.net_io_counters().bytes_recv,
}
}
def updateNetwork():
global network
network = {
"currentTime": datetime.now(),
"out": {
"currentByte": psutil.net_io_counters().bytes_sent,
},
"in": {
"currentByte": psutil.net_io_counters().bytes_recv,
}
}
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"] and getTimeDelta(datetime.now(), doCleanTime) > 7200) or _force:
doCleanTime = datetime.now()
_list = sorted(glob("*.flv"), key=lambda x: datetime.utcfromtimestamp(os.path.getmtime(x)))
for _i in _list:
if not os.path.exists(_i):
break
doCleanTime = datetime.now()
if (datetime.now() - datetime.utcfromtimestamp(os.path.getmtime(_i))).days >= config["exp"]:
_clean_flag = True
if config["dow"] == "bypy":
_res = bypy.upload(_i)
if _res == 0:
os.remove(_i)
else:
os.system(config["dow"])
else:
break
doCleanTime = datetime.now()
_clean_flag = False
def doClean(_force=False):
if _clean_flag:
appendError("doClean request on cleaning, will ignore it")
return
p = threading.Thread(target=_doClean, args=(_force,))
p.setDaemon(True)
p.start()
def getCurrentStatus():
_disk = psutil.disk_usage(".")
_mem = psutil.virtual_memory()
_delta= getTimeDelta(datetime.now(),network["currentTime"])
_net = psutil.net_io_counters()
if 60 > _delta > 0:
_inSpeed = (_net.bytes_recv - network["in"]["currentByte"]) / _delta
_outSpeed = (_net.bytes_sent - network["out"]["currentByte"]) / _delta
else:
_outSpeed = 0
_inSpeed = 0
updateNetwork()
if getTimeDelta(datetime.now(), doCleanTime) > 3600:
doClean()
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"],
}
def reloadConfig():
global config, _config_fp
_config_fp = open("config.json", "r", encoding="utf8")
config = json.load(_config_fp)
_config_fp.close()
dt_format = "%Y/%m/%d %H:%M:%S"
broadcaster = ""
streamUrl = ""
isBroadcasting = False
updateTime = ""
forceNotDownload = False
forceNotBroadcasting = False
forceNotUpload = False
forceNotEncode = False
forceStartEncodeThread = False
forceStartUploadThread = False
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"]:]
class downloader(XiGuaLiveApi):
files = []
playlist = None
def updRoomInfo(self):
global broadcaster, isBroadcasting, updateTime, forceNotBroadcasting, forceNotDownload
super(downloader, self).updRoomInfo()
updateTime = datetime.strftime(datetime.now(), dt_format)
broadcaster = self.roomLiver
isBroadcasting = self.isLive
if self.isLive:
self.updPlayList()
else:
forceNotDownload = False
forceNotBroadcasting = False
self.playlist = False
self.files = []
def updPlayList(self):
global streamUrl
if self.isLive:
if "stream_url" in self._rawRoomInfo:
if self.playlist is None:
self.playlist = False
else:
self.playlist = self._rawRoomInfo["stream_url"]["flv_pull_url"]
self.playlist = self.playlist.replace("_uhd", "").replace("_sd", "").replace("_ld", "")
streamUrl = self.playlist
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"])

70
CursesDownload.py Normal file
View File

@ -0,0 +1,70 @@
import curses
import Common
widths = [
(126, 1), (159, 0), (687, 1), (710, 0), (711, 1),
(727, 0), (733, 1), (879, 0), (1154, 1), (1161, 0),
(4347, 1), (4447, 2), (7467, 1), (7521, 0), (8369, 1),
(8426, 0), (9000, 1), (9002, 2), (11021, 1), (12350, 2),
(12351, 1), (12438, 2), (12442, 0), (19893, 2), (19967, 1),
(55203, 2), (63743, 1), (64106, 2), (65039, 1), (65059, 0),
(65131, 2), (65279, 1), (65376, 2), (65500, 1), (65510, 2),
(120831, 1), (262141, 2), (1114109, 1),
]
def get_width(o):
global widths
if o == 0xe or o == 0xf:
return 0
for num, wid in widths:
if o <= num:
return wid
return 1
def c_print(handle, y, x, string, style=curses.A_NORMAL):
if type(string) != str:
string = str(string)
for _i in string:
_w = get_width(ord(_i))
if(_w>1):
handle.addch(y, x+1, " ", style)
handle.addch(y, x, ord(_i), style)
x += _w
def render(screen):
_style = curses.A_DIM
if Common.api.isLive:
_style = curses.A_BOLD | curses.A_BLINK | curses.A_ITALIC | curses.A_UNDERLINE
c_print(screen, 1, 3, Common.api.roomLiver, _style)
screen.refresh()
def main(stdscr):
global screen
screen = stdscr.subwin(23, 79, 0, 0)
screen.timeout(2000)
screen.box()
screen.hline(2, 1, curses.ACS_HLINE, 77)
c_print(screen, 1, 2, " "*45 + " 西瓜录播助手 -- by JerryYan ", curses.A_STANDOUT)
render(screen)
while True:
c = stdscr.getch()
if c == ord("q"):
break
elif c == ord("f"):
render(screen)
stdscr = curses.initscr()
curses.noecho()
curses.cbreak()
stdscr.keypad(1)
curses.wrapper(main)
stdscr.keypad(0)
curses.echo()
curses.nocbreak()
curses.endwin()

107
CursesMain.py Normal file
View File

@ -0,0 +1,107 @@
import curses
from Struct.Chat import Chat
from Struct.Gift import Gift
from Struct.MemberMsg import MemberMsg
from Struct.User import User
from api import XiGuaLiveApi
class Api(XiGuaLiveApi):
danmakuList = []
def onAd(self, i):
pass
def onChat(self, chat: Chat):
self.danmakuList.append(str(chat))
def onLike(self, user: User):
pass
def onEnter(self, msg: MemberMsg):
pass
def onJoin(self, user: User):
self.danmakuList.append(str(user))
def onSubscribe(self, user: User):
self.danmakuList.append(str(user))
def onPresent(self, gift: Gift):
pass
def onPresentEnd(self, gift: Gift):
self.danmakuList.append(str(gift))
api = Api()
widths = [
(126, 1), (159, 0), (687, 1), (710, 0), (711, 1),
(727, 0), (733, 1), (879, 0), (1154, 1), (1161, 0),
(4347, 1), (4447, 2), (7467, 1), (7521, 0), (8369, 1),
(8426, 0), (9000, 1), (9002, 2), (11021, 1), (12350, 2),
(12351, 1), (12438, 2), (12442, 0), (19893, 2), (19967, 1),
(55203, 2), (63743, 1), (64106, 2), (65039, 1), (65059, 0),
(65131, 2), (65279, 1), (65376, 2), (65500, 1), (65510, 2),
(120831, 1), (262141, 2), (1114109, 1),
]
def get_width(o):
global widths
if o == 0xe or o == 0xf:
return 0
for num, wid in widths:
if o <= num:
return wid
return 1
def c_print(handle, y, x, string, style=curses.A_NORMAL):
if type(string) != str:
string = str(string)
for _i in string:
_w = get_width(ord(_i))
if(_w>1):
handle.addch(y, x+1, " ", style)
if _i != " " or style!=curses.A_NORMAL:
handle.addch(y, x, ord(_i), style)
else:
handle.addch(y, x, 0, style)
x += _w
def render(screen):
screen.erase()
screen.box()
screen.hline(2, 1, curses.ACS_HLINE, 77)
c_print(screen, 1, 2, " "*45 + " 西瓜弹幕助手 -- by JerryYan ", curses.A_STANDOUT)
_style = curses.A_DIM
if api.isLive:
_style = curses.A_BOLD | curses.A_BLINK | curses.A_ITALIC
c_print(screen, 1, 3, api.roomLiver, _style)
_y = 3
api.getDanmaku()
for i in api.danmakuList[-10:]:
c_print(screen, _y, 2, i)
_y += 1
screen.move(0,0)
screen.refresh()
def main(stdscr):
global screen
screen = stdscr.subwin(23, 79, 0, 0)
screen.timeout(2000)
render(screen)
while True:
c = screen.getch()
if c == ord("q"):
break
render(screen)
stdscr = curses.initscr()
curses.noecho()
curses.cbreak()
stdscr.keypad(1)
curses.wrapper(main)
stdscr.keypad(0)
curses.echo()
curses.nocbreak()
curses.endwin()

8
Model/DataBase.py Normal file
View File

@ -0,0 +1,8 @@
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URL"] = "sqlite://data.db"
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
db = SQLAlchemy(app)

10
Model/Files.py Normal file
View File

@ -0,0 +1,10 @@
from datetime import datetime
from sqlalchemy import func
from .DataBase import db
class Files(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
filename = db.Column(db.String(50))
is_upload = db.Column(db.Integer(1), server_default=0, default=0)
create_time = db.Column(db.TIMESTAMP, server_default=func.now(), default=datetime.now())

View File

@ -5,17 +5,12 @@
### 西瓜直播弹幕助手--录播端```WebMain.py``` ### 西瓜直播弹幕助手--录播端```WebMain.py```
- 能够自动进行ffmpeg转码 > - 能够自动进行ffmpeg转码
> - 转码后自动上传至B站
- 转码后自动上传至B站 > - 顺便还能自己清理录播的文件(移动到一个位置,执行shell命令,上传百度云)
> - 把录像文件分一定大小保存(B站有限制,但是不知道是多少)
- 顺便还能自己清理录播的文件(移动到一个位置,执行shell命令,上传百度云) > - 少部分错误包容机制
> - 有一个简单的WEB页面,及简单的控制接口
- 把录像文件分一定大小保存(B站有限制,但是不知道是多少)
- 少部分错误包容机制
- 有一个简单的WEB页面,及简单的控制接口
### 西瓜直播弹幕助手--礼物端```WinMain.py``` ### 西瓜直播弹幕助手--礼物端```WinMain.py```

View File

@ -4,9 +4,9 @@ from .Lottery import Lottery
class Chat: class Chat:
content: str ="" content =""
user: User=None user=None
filterString:list = ["",] filterString = ["",]
isFiltered = False isFiltered = False
def __init__(self, json=None, lottery:Lottery = None): def __init__(self, json=None, lottery:Lottery = None):

View File

@ -3,12 +3,12 @@ from .User import User
class Gift: class Gift:
ID:int = 0 ID = 0
count:int = 0 count = 0
roomID:int = 0 roomID = 0
giftList:dict = {} giftList = {}
amount:int = 0 amount = 0
user:User = None user = None
def __init__(self, json=None): def __init__(self, json=None):
if json: if json:

View File

@ -5,14 +5,14 @@ from .LuckyUser import LuckyUser
class Lottery: class Lottery:
ID: int = 0 ID = 0
isActive = False isActive = False
content = "" content = ""
isFinished = False isFinished = False
luckyUsers = [] luckyUsers = []
joinedUserCount = 0 joinedUserCount = 0
prizeName = "" prizeName = ""
finish:int = 0 finish = 0
def __init__(self, json=None): def __init__(self, json=None):
if json: if json:

View File

@ -2,9 +2,9 @@ from .User import User
class MemberMsg: class MemberMsg:
type:int = 0 type = 0
content:str = "" content = ""
user:User = None user = None
def __init__(self, json=None): def __init__(self, json=None):
if json: if json:

View File

@ -1,11 +1,11 @@
class User: class User:
ID: int = 0 ID = 0
name: str = "" name = ""
brand: str = "" brand = ""
level: int = 0 level = 0
type: int = 0 type = 0
block: bool = False block = False
mute: bool = False mute = False
def __init__(self, json=None): def __init__(self, json=None):
if json: if json:
@ -47,8 +47,7 @@ class User:
else: else:
if self.type != 0: if self.type != 0:
return "[{}{}]{}".format(self.brand, self.level, self.name) return "[{}{}]{}".format(self.brand, self.level, self.name)
return "<{}{}>{}".format(self.brand,self.level,self.name) return "<{}{}>{}".format(self.brand, self.level, self.name)
def __unicode__(self): def __unicode__(self):
return self.__str__() return self.__str__()

260
WebMain.py Normal file
View File

@ -0,0 +1,260 @@
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)
# url_for('static', filename='index.html')
# url_for('static', filename='index.js')
@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
},
"config": {
"forceNotBroadcasting": Common.forceNotBroadcasting,
"forceNotDownload": Common.forceNotDownload,
"forceNotUpload": Common.forceNotUpload,
"forceNotEncode": Common.forceNotEncode,
},
}})
@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
}
}})
@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,
}
}})
@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("/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

@ -185,10 +185,14 @@ if __name__ == "__main__":
else: else:
name = readInput("请输入主播用户名,默认为", name, 3) name = readInput("请输入主播用户名,默认为", name, 3)
api = WinMain(name) api = WinMain(name)
print("进入", api.roomLiver, "的直播间") while not api.isValidRoom:
if not api.isValidRoom: set_cmd_text_color(FOREGROUND_RED)
input("房间不存在") print("未找到对应房间或未开播,等待1分钟后重试")
resetColor()
time.sleep(60)
api.updRoomInfo()
sys.exit() sys.exit()
print("进入", api.roomLiver, "的直播间")
os.system("title {}".format(api.getTitle())) os.system("title {}".format(api.getTitle()))
print("=" * 30) print("=" * 30)
while True: while True:

27
api.py
View File

@ -12,21 +12,21 @@ import time
s = requests.Session() s = requests.Session()
DEBUG: bool = False DEBUG = False
class XiGuaLiveApi: class XiGuaLiveApi:
isLive: bool = False isLive = False
isValidRoom: bool = False isValidRoom = False
_rawRoomInfo = {} _rawRoomInfo = {}
name: str = "" name = ""
roomID: int = 0 roomID = 0
roomTitle: str = "" roomTitle = ""
roomLiver: User = None roomLiver = None
roomPopularity: int = 0 roomPopularity = 0
_cursor:str = "0" _cursor = "0"
_updRoomCount:int = 0 _updRoomCount = 0
lottery:Lottery = None lottery = None
def __init__(self, name: str = "永恒de草薙"): def __init__(self, name: str = "永恒de草薙"):
self.name = name self.name = name
@ -161,9 +161,12 @@ class XiGuaLiveApi:
if "room" not in d and d["room"] is None: if "room" not in d and d["room"] is None:
self.apiChangedError("Api发生改变,请及时联系我", d) self.apiChangedError("Api发生改变,请及时联系我", d)
return False return False
self.roomLiver = User(d)
if self.name not in str(self.roomLiver):
self.isLive = False
return False
self._rawRoomInfo = d["room"] self._rawRoomInfo = d["room"]
self.isLive = d["room"]["status"] == 2 self.isLive = d["room"]["status"] == 2
self.roomLiver = User(d)
self.roomTitle = d["room"]["title"] self.roomTitle = d["room"]["title"]
self.roomPopularity = d["room"]["user_count"] self.roomPopularity = d["room"]["user_count"]
l = Lottery(d) l = Lottery(d)

View File

@ -4,7 +4,8 @@ import os
import re import re
import json as JSON import json as JSON
from datetime import datetime from datetime import datetime
from time import sleep
import Common
import rsa import rsa
import math import math
import base64 import base64
@ -25,6 +26,7 @@ class Bilibili:
self.files = [] self.files = []
self.videos = [] self.videos = []
self.session = requests.session() self.session = requests.session()
self.session.keep_alive = False
if cookie: if cookie:
self.session.headers["cookie"] = cookie self.session.headers["cookie"] = cookie
self.csrf = re.search('bili_jct=(.*?);', cookie).group(1) self.csrf = re.search('bili_jct=(.*?);', cookie).group(1)
@ -188,14 +190,13 @@ class Bilibili:
""" """
self.preUpload(parts) self.preUpload(parts)
self.finishUpload(title, tid, tag, desc, source, cover, no_reprint) self.finishUpload(title, tid, tag, desc, source, cover, no_reprint)
self.clean() self.clear()
def preUpload(self, parts): def preUpload(self, parts):
""" """
:param parts: e.g. VideoPart('part path', 'part title', 'part desc'), or [VideoPart(...), VideoPart(...)] :param parts: e.g. VideoPart('part path', 'part title', 'part desc'), or [VideoPart(...), VideoPart(...)]
:type parts: VideoPart or list<VideoPart> :type parts: VideoPart or list<VideoPart>
""" """
self.session.headers['Content-Type'] = 'application/json; charset=utf-8' self.session.headers['Content-Type'] = 'application/json; charset=utf-8'
if not isinstance(parts, list): if not isinstance(parts, list):
parts = [parts] parts = [parts]
@ -204,6 +205,7 @@ class Bilibili:
filepath = part.path filepath = part.path
filename = os.path.basename(filepath) filename = os.path.basename(filepath)
filesize = os.path.getsize(filepath) filesize = os.path.getsize(filepath)
Common.appendUploadStatus("Upload >{}< Started".format(filepath))
self.files.append(part) self.files.append(part)
r = self.session.get('https://member.bilibili.com/preupload?' r = self.session.get('https://member.bilibili.com/preupload?'
'os=upos&upcdn=ws&name={name}&size={size}&r=upos&profile=ugcupos%2Fyb&ssl=0' 'os=upos&upcdn=ws&name={name}&size={size}&r=upos&profile=ugcupos%2Fyb&ssl=0'
@ -238,12 +240,13 @@ class Bilibili:
# {"upload_id":"72eb747b9650b8c7995fdb0efbdc2bb6","key":"\/i181012ws2wg1tb7tjzswk2voxrwlk1u.mp4","OK":1,"bucket":"ugc"} # {"upload_id":"72eb747b9650b8c7995fdb0efbdc2bb6","key":"\/i181012ws2wg1tb7tjzswk2voxrwlk1u.mp4","OK":1,"bucket":"ugc"}
json = r.json() json = r.json()
upload_id = json['upload_id'] upload_id = json['upload_id']
with open(filepath, 'rb') as f: with open(filepath, 'rb') as f:
chunks_num = math.ceil(filesize / chunk_size) chunks_num = math.ceil(filesize / chunk_size)
chunks_index = 0 chunks_index = 0
chunks_data = f.read(chunk_size) chunks_data = f.read(chunk_size)
Common.modifyLastUploadStatus("Uploading >{}< @ {:.2f}%".format(filepath, 100.0 * chunks_index / chunks_num))
while True: while True:
_d = datetime.now()
if not chunks_data: if not chunks_data:
break break
r = self.session.put('https:{endpoint}/{upos_uri}?' r = self.session.put('https:{endpoint}/{upos_uri}?'
@ -263,10 +266,11 @@ class Bilibili:
) )
if r.status_code != 200: if r.status_code != 200:
continue continue
print('{} : UPLOAD {}/{}'.format(datetime.strftime(datetime.now(), "%y%m%d %H%M"), chunks_index,
chunks_num), r.text)
chunks_data = f.read(chunk_size) chunks_data = f.read(chunk_size)
chunks_index += 1 # start with 0 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 # NOT DELETE! Refer to https://github.com/comwrg/bilibiliupload/issues/15#issuecomment-424379769
self.session.post('https:{endpoint}/{upos_uri}?' self.session.post('https:{endpoint}/{upos_uri}?'
@ -282,6 +286,7 @@ class Bilibili:
self.videos.append({'filename': upos_uri.replace('upos://ugc/', '').split('.')[0], self.videos.append({'filename': upos_uri.replace('upos://ugc/', '').split('.')[0],
'title': part.title, 'title': part.title,
'desc': part.desc}) 'desc': part.desc})
Common.modifyLastUploadStatus("Upload >{}< Finished".format(filepath))
__f = open("uploaded.json","w") __f = open("uploaded.json","w")
JSON.dump(self.videos, __f) JSON.dump(self.videos, __f)
@ -314,6 +319,7 @@ class Bilibili:
""" """
if len(self.videos) == 0: if len(self.videos) == 0:
return return
Common.appendUploadStatus("[{}]投稿中,请稍后".format(title))
self.session.headers['Content-Type'] = 'application/json; charset=utf-8' self.session.headers['Content-Type'] = 'application/json; charset=utf-8'
copyright = 2 if source else 1 copyright = 2 if source else 1
r = self.session.post('https://member.bilibili.com/x/vu/web/add?csrf=' + self.csrf, r = self.session.post('https://member.bilibili.com/x/vu/web/add?csrf=' + self.csrf,
@ -330,21 +336,21 @@ class Bilibili:
"order_id": 0, "order_id": 0,
"videos": self.videos} "videos": self.videos}
) )
print(r.text) Common.modifyLastUploadStatus("[{}] Published | Result : {}".format(title, r.text))
def reloadFromPrevious(self): def reloadFromPrevious(self):
if os.path.exists("uploaded.json"): if os.path.exists("uploaded.json"):
__f = open("uploaded.json", "r") __f = open("uploaded.json", "r")
try: try:
self.videos = JSON.load(__f) self.videos = JSON.load(__f)
print("RELOAD Success") Common.appendUploadStatus("RELOAD SUCCESS")
except: except:
print("RELOAD Failed") Common.appendUploadStatus("RELOAD Failed")
self.videos = [] self.videos = []
__f.close() __f.close()
os.remove("uploaded.json") os.remove("uploaded.json")
else: else:
print("RELOAD Failed") Common.appendUploadStatus("RELOAD Failed")
self.videos = [] self.videos = []
def clear(self): def clear(self):

View File

@ -2,71 +2,14 @@ import shutil
import sys import sys
import time import time
from datetime import datetime from datetime import datetime
import queue
import threading import threading
from config import config
from api import XiGuaLiveApi
from bilibili import * from bilibili import *
import Common
import os
import requests
q = queue.Queue()
base_uri = ""
isEncode = False isEncode = False
isDownload = False isDownload = False
uq = queue.Queue()
eq = queue.Queue()
class downloader(XiGuaLiveApi):
files = []
playlist: str = None
def updRoomInfo(self):
super(downloader, self).updRoomInfo()
if self.isLive:
self.updPlayList()
else:
print("未开播,等待开播")
self.files = []
def updPlayList(self):
if self.isLive:
if "stream_url" in self._rawRoomInfo:
if self.playlist is None:
self.apiChangedError("无法获取直播链接")
self.playlist = False
else:
self.playlist = self._rawRoomInfo["stream_url"]["flv_pull_url"]
self.playlist = self.playlist.replace("_uhd", "").replace("_sd", "").replace("_ld", "")
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
def download(url): def download(url):
@ -74,100 +17,129 @@ def download(url):
path = datetime.strftime(datetime.now(), "%Y%m%d_%H%M.flv") path = datetime.strftime(datetime.now(), "%Y%m%d_%H%M.flv")
p = requests.get(url, stream=True) p = requests.get(url, stream=True)
if p.status_code != 200: if p.status_code != 200:
print("{} : Download Response 404 ,will stop looping".format(datetime.strftime(datetime.now(), "%y%m%d %H%M"))) Common.appendDownloadStatus("Download with Response 404, maybe broadcaster is not broadcasting")
return True return True
isDownload = True isDownload = True
print("{} : Download {}".format(datetime.strftime(datetime.now(), "%y%m%d %H%M"), path)) Common.appendDownloadStatus("Download >{}< Start".format(path))
f = open(path, "wb") f = open(path, "wb")
try: try:
for t in p.iter_content(chunk_size=64 * 1024): for t in p.iter_content(chunk_size=64 * 1024):
if t: if t:
f.write(t) f.write(t)
if os.path.getsize(path) > 1024 * 1024 * 1024 * 1.5: else:
raise Exception("`t` is not valid")
_size = os.path.getsize(path)
Common.modifyLastDownloadStatus("Downloading >{}< @ {:.2f}%".format(path, 100.0 * _size/Common.config["p_s"]))
if _size > Common.config["p_s"] or Common.forceNotDownload:
Common.modifyLastDownloadStatus("Download >{}< Exceed MaxSize".format(path))
break break
print("{} : Download Quiting".format(datetime.strftime(datetime.now(), "%y%m%d %H%M")))
except Exception as e: except Exception as e:
print("{} : Download Quiting With Exception {}".format(datetime.strftime(datetime.now(), "%y%m%d %H%M"), Common.appendError("Download >{}< With Exception {}".format(path, e.__str__()))
e.__str__()))
f.close() f.close()
isDownload = False isDownload = False
if os.path.getsize(path) == 0: Common.modifyLastDownloadStatus("Download >{}< Finished".format(path))
if os.path.getsize(path) < 1024 * 1024:
Common.modifyLastDownloadStatus("Downloaded File >{}< is too small, will ignore it".format(path))
os.remove(path) os.remove(path)
return False return False
eq.put(path) if Common.forceNotDownload:
Common.modifyLastDownloadStatus("设置了不下载,所以[{}]不会下载".format(path))
return
else:
Common.encodeQueue.put(path)
download(url) download(url)
def encode(): def encode():
global isEncode global isEncode
Common.appendEncodeStatus("Encode Daemon Starting")
while True: while True:
i = eq.get() isEncode = False
i = Common.encodeQueue.get()
if Common.forceNotEncode:
Common.appendEncodeStatus("设置了不编码,所以[{}]不会编码".format(i))
Common.uploadQueue.put(i)
continue
if os.path.exists(i): if os.path.exists(i):
isEncode = True isEncode = True
os.system("ffmpeg -i {} -c:v copy -c:a copy -f mp4 {}".format(i, i[:13] + ".mp4")) if os.path.getsize(i) < 8 * 1024 * 1024:
uq.put(i[:13] + ".mp4") Common.appendEncodeStatus("Encoded File >{}< is too small, will ignore it".format(i))
if config["mv"]: continue
shutil.move(i, config["mtd"]) Common.appendEncodeStatus("Encoding >{}< Start".format(i))
elif config["del"]: os.system("ffmpeg -i {} -c:v copy -c:a copy -f mp4 {} -y".format(i, i[:13] + ".mp4"))
os.remove(i) Common.uploadQueue.put(i[:13] + ".mp4")
isEncode = False Common.modifyLastEncodeStatus("Encode >{}< Finished".format(i))
def upload(date=datetime.strftime(datetime.now(), "%Y_%m_%d")): def upload(date=datetime.strftime(datetime.now(), "%Y_%m_%d")):
print("{} : Upload Daemon Starting".format(datetime.strftime(datetime.now(), "%y%m%d %H%M"))) Common.appendUploadStatus("Upload Daemon Starting")
i = uq.get() i = Common.uploadQueue.get()
while True: while True:
Common.doClean()
if Common.forceNotUpload:
if isinstance(i, bool):
Common.appendUploadStatus("设置了不上传,不会发布了")
return
Common.appendUploadStatus("设置了不上传,所以[{}]不会上传了".format(i))
i = Common.uploadQueue.get()
continue
if isinstance(i, bool): if isinstance(i, bool):
print("{} : Upload Daemon Receive Command {}"
.format(datetime.strftime(datetime.now(), "%y%m%d %H%M"), i))
if i is True: if i is True:
print("自动投稿中,请稍后") b.finishUpload(Common.config["t_t"].format(date), 17, Common.config["tag"], Common.config["des"],
b.finishUpload(config["t_t"].format(date), 17, config["tag"], config["des"], source=Common.config["src"], no_reprint=0)
source=config["src"], no_reprint=0)
b.clear() b.clear()
break break
print("{} : Upload {}".format(datetime.strftime(datetime.now(), "%y%m%d %H%M"), i))
if not os.path.exists(i): if not os.path.exists(i):
print("{} : Upload File Not Exist {}".format(datetime.strftime(datetime.now(), "%y%m%d %H%M"), i)) Common.appendError("Upload File Not Exist {}".format(i))
i = uq.get() i = Common.uploadQueue.get()
continue continue
try: try:
b.preUpload(VideoPart(i, os.path.basename(i))) b.preUpload(VideoPart(i, os.path.basename(i)))
except: except Exception as e:
Common.appendError(e.__str__())
continue continue
if not Common.forceNotEncode:
os.remove(i) os.remove(i)
i = uq.get() i = Common.uploadQueue.get()
Common.appendUploadStatus("Upload Daemon Quiting")
print("{} : Upload Daemon Quiting".format(datetime.strftime(datetime.now(), "%y%m%d %H%M")))
b = Bilibili() b = Bilibili()
b.login(config["b_u"], config["b_p"]) b.login(Common.config["b_u"], Common.config["b_p"])
if __name__ == "__main__": et = threading.Thread(target=encode, args=())
name = config["l_u"] et.setDaemon(True)
print("西瓜直播录播助手 by JerryYan") et.start()
api = downloader(name)
print("进入", api.roomLiver, "的直播间")
if not api.isValidRoom: def run():
input("房间不存在") global isEncode, isDownload, et
sys.exit() Common.refreshDownloader()
print("=" * 30) if not Common.api.isValidRoom:
d = datetime.strftime(datetime.now(), "%Y_%m_%d") Common.appendError("[{}]房间未找到".format(Common.config["l_u"]))
return
d = None
t = threading.Thread(target=download) t = threading.Thread(target=download)
ut = threading.Thread(target=upload, args=(d,)) ut = threading.Thread(target=upload, args=(d,))
et = threading.Thread(target=encode, args=())
et.setDaemon(True)
et.start()
_count = 0 _count = 0
_count_error = 0 _count_error = 0
while True: while True:
if api.isLive: if Common.api.isLive and not Common.forceNotBroadcasting:
if d is None: if d is None:
d = datetime.strftime(datetime.now(), "%Y_%m_%d") d = datetime.strftime(datetime.now(), "%Y_%m_%d")
if not t.is_alive(): if not t.is_alive() and not Common.forceNotDownload:
try:
Common.api.updRoomInfo()
_count = 0
_count_error = 0
except Exception as e:
Common.appendError(e.__str__())
continue
_count_error += 1 _count_error += 1
_preT = api.playlist _preT = Common.api.playlist
if not _preT:
Common.api.updRoomInfo()
continue
t = threading.Thread(target=download, args=(_preT,)) t = threading.Thread(target=download, args=(_preT,))
t.setDaemon(True) t.setDaemon(True)
t.start() t.start()
@ -181,31 +153,45 @@ if __name__ == "__main__":
et.start() et.start()
if _count % 15 == 0: if _count % 15 == 0:
try: try:
api.updRoomInfo() Common.api.updRoomInfo()
_count = 0 _count = 0
_count_error = 0 _count_error = 0
except Exception as e: except Exception as e:
print(e.__str__()) Common.appendError(e.__str__())
time.sleep(20) time.sleep(20)
_count_error += 1 _count_error += 1
continue continue
if _count_error > 15: if _count_error > 15:
api.isLive = False Common.api.isLive = False
_count += 1 _count += 1
time.sleep(20) time.sleep(20)
else: else:
if d is not None: if d is not None:
d = None d = None
if not isEncode and not isDownload: if not isEncode and not isDownload:
uq.put(True) Common.uploadQueue.put(True)
isEncode = True isEncode = True
isDownload = True isDownload = True
del config
from config import config
# print("主播未开播,等待1分钟后重试") # print("主播未开播,等待1分钟后重试")
time.sleep(60) time.sleep(60)
try: try:
api.updRoomInfo() Common.api.updRoomInfo()
_count_error = 0 _count_error = 0
except Exception as e: except Exception as e:
print(e.__str__()) Common.appendError(e.__str__())
Common.refreshDownloader()
if not Common.api.roomLiver:
Common.refreshDownloader()
if Common.forceStartEncodeThread:
if not et.is_alive():
et = threading.Thread(target=encode, args=())
et.setDaemon(True)
et.start()
Common.forceStartEncodeThread = False
if Common.forceStartUploadThread:
if not ut.is_alive():
d = datetime.strftime(datetime.now(), "%Y_%m_%d")
ut = threading.Thread(target=upload, args=(d,))
ut.setDaemon(True)
ut.start()
Common.forceStartUploadThread = False

26
static/device.js Normal file
View File

@ -0,0 +1,26 @@
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,4000)

57
static/index.js Normal file
View File

@ -0,0 +1,57 @@
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)
$("#forceNotBroadcasting").text(res.data.config.forceNotBroadcasting)
$("#forceNotDownload").text(res.data.config.forceNotDownload)
$("#forceNotUpload").text(res.data.config.forceNotUpload)
$("#forceNotEncode").text(res.data.config.forceNotEncode)
$("#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,10000)

29
templates/device.html Normal file
View File

@ -0,0 +1,29 @@
<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>

26
templates/files.html Normal file
View File

@ -0,0 +1,26 @@
<!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>

13
templates/head.html Normal file
View File

@ -0,0 +1,13 @@
<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>

78
templates/index.html Normal file
View File

@ -0,0 +1,78 @@
<!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>
</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>
</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>