Merge branch 'with_api'
# Conflicts: # README.md
This commit is contained in:
commit
a6aafd0ef2
322
Common.py
Normal file
322
Common.py
Normal 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
70
CursesDownload.py
Normal 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
107
CursesMain.py
Normal 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
8
Model/DataBase.py
Normal 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
10
Model/Files.py
Normal 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())
|
17
README.md
17
README.md
@ -5,17 +5,12 @@
|
||||
|
||||
### 西瓜直播弹幕助手--录播端```WebMain.py```
|
||||
|
||||
- 能够自动进行ffmpeg转码
|
||||
|
||||
- 转码后自动上传至B站
|
||||
|
||||
- 顺便还能自己清理录播的文件(移动到一个位置,执行shell命令,上传百度云)
|
||||
|
||||
- 把录像文件分一定大小保存(B站有限制,但是不知道是多少)
|
||||
|
||||
- 少部分错误包容机制
|
||||
|
||||
- 有一个简单的WEB页面,及简单的控制接口
|
||||
> - 能够自动进行ffmpeg转码
|
||||
> - 转码后自动上传至B站
|
||||
> - 顺便还能自己清理录播的文件(移动到一个位置,执行shell命令,上传百度云)
|
||||
> - 把录像文件分一定大小保存(B站有限制,但是不知道是多少)
|
||||
> - 少部分错误包容机制
|
||||
> - 有一个简单的WEB页面,及简单的控制接口
|
||||
|
||||
### 西瓜直播弹幕助手--礼物端```WinMain.py```
|
||||
|
||||
|
@ -4,9 +4,9 @@ from .Lottery import Lottery
|
||||
|
||||
class Chat:
|
||||
|
||||
content: str =""
|
||||
user: User=None
|
||||
filterString:list = ["",]
|
||||
content =""
|
||||
user=None
|
||||
filterString = ["",]
|
||||
isFiltered = False
|
||||
|
||||
def __init__(self, json=None, lottery:Lottery = None):
|
||||
|
@ -3,12 +3,12 @@ from .User import User
|
||||
|
||||
|
||||
class Gift:
|
||||
ID:int = 0
|
||||
count:int = 0
|
||||
roomID:int = 0
|
||||
giftList:dict = {}
|
||||
amount:int = 0
|
||||
user:User = None
|
||||
ID = 0
|
||||
count = 0
|
||||
roomID = 0
|
||||
giftList = {}
|
||||
amount = 0
|
||||
user = None
|
||||
|
||||
def __init__(self, json=None):
|
||||
if json:
|
||||
|
@ -5,14 +5,14 @@ from .LuckyUser import LuckyUser
|
||||
|
||||
|
||||
class Lottery:
|
||||
ID: int = 0
|
||||
ID = 0
|
||||
isActive = False
|
||||
content = ""
|
||||
isFinished = False
|
||||
luckyUsers = []
|
||||
joinedUserCount = 0
|
||||
prizeName = ""
|
||||
finish:int = 0
|
||||
finish = 0
|
||||
|
||||
def __init__(self, json=None):
|
||||
if json:
|
||||
|
@ -2,9 +2,9 @@ from .User import User
|
||||
|
||||
|
||||
class MemberMsg:
|
||||
type:int = 0
|
||||
content:str = ""
|
||||
user:User = None
|
||||
type = 0
|
||||
content = ""
|
||||
user = None
|
||||
|
||||
def __init__(self, json=None):
|
||||
if json:
|
||||
|
@ -1,11 +1,11 @@
|
||||
class User:
|
||||
ID: int = 0
|
||||
name: str = ""
|
||||
brand: str = ""
|
||||
level: int = 0
|
||||
type: int = 0
|
||||
block: bool = False
|
||||
mute: bool = False
|
||||
ID = 0
|
||||
name = ""
|
||||
brand = ""
|
||||
level = 0
|
||||
type = 0
|
||||
block = False
|
||||
mute = False
|
||||
|
||||
def __init__(self, json=None):
|
||||
if json:
|
||||
@ -47,8 +47,7 @@ class User:
|
||||
else:
|
||||
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)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.__str__()
|
||||
|
||||
|
260
WebMain.py
Normal file
260
WebMain.py
Normal 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()
|
10
WinMain.py
10
WinMain.py
@ -185,10 +185,14 @@ if __name__ == "__main__":
|
||||
else:
|
||||
name = readInput("请输入主播用户名,默认为", name, 3)
|
||||
api = WinMain(name)
|
||||
print("进入", api.roomLiver, "的直播间")
|
||||
if not api.isValidRoom:
|
||||
input("房间不存在")
|
||||
while not api.isValidRoom:
|
||||
set_cmd_text_color(FOREGROUND_RED)
|
||||
print("未找到对应房间或未开播,等待1分钟后重试")
|
||||
resetColor()
|
||||
time.sleep(60)
|
||||
api.updRoomInfo()
|
||||
sys.exit()
|
||||
print("进入", api.roomLiver, "的直播间")
|
||||
os.system("title {}".format(api.getTitle()))
|
||||
print("=" * 30)
|
||||
while True:
|
||||
|
27
api.py
27
api.py
@ -12,21 +12,21 @@ import time
|
||||
|
||||
s = requests.Session()
|
||||
|
||||
DEBUG: bool = False
|
||||
DEBUG = False
|
||||
|
||||
|
||||
class XiGuaLiveApi:
|
||||
isLive: bool = False
|
||||
isValidRoom: bool = False
|
||||
isLive = False
|
||||
isValidRoom = False
|
||||
_rawRoomInfo = {}
|
||||
name: str = ""
|
||||
roomID: int = 0
|
||||
roomTitle: str = ""
|
||||
roomLiver: User = None
|
||||
roomPopularity: int = 0
|
||||
_cursor:str = "0"
|
||||
_updRoomCount:int = 0
|
||||
lottery:Lottery = None
|
||||
name = ""
|
||||
roomID = 0
|
||||
roomTitle = ""
|
||||
roomLiver = None
|
||||
roomPopularity = 0
|
||||
_cursor = "0"
|
||||
_updRoomCount = 0
|
||||
lottery = None
|
||||
|
||||
def __init__(self, name: str = "永恒de草薙"):
|
||||
self.name = name
|
||||
@ -161,9 +161,12 @@ class XiGuaLiveApi:
|
||||
if "room" not in d and d["room"] is None:
|
||||
self.apiChangedError("Api发生改变,请及时联系我", d)
|
||||
return False
|
||||
self.roomLiver = User(d)
|
||||
if self.name not in str(self.roomLiver):
|
||||
self.isLive = False
|
||||
return False
|
||||
self._rawRoomInfo = d["room"]
|
||||
self.isLive = d["room"]["status"] == 2
|
||||
self.roomLiver = User(d)
|
||||
self.roomTitle = d["room"]["title"]
|
||||
self.roomPopularity = d["room"]["user_count"]
|
||||
l = Lottery(d)
|
||||
|
26
bilibili.py
26
bilibili.py
@ -4,7 +4,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
|
||||
@ -25,6 +26,7 @@ class Bilibili:
|
||||
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)
|
||||
@ -188,14 +190,13 @@ class Bilibili:
|
||||
"""
|
||||
self.preUpload(parts)
|
||||
self.finishUpload(title, tid, tag, desc, source, cover, no_reprint)
|
||||
self.clean()
|
||||
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]
|
||||
@ -204,6 +205,7 @@ class Bilibili:
|
||||
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'
|
||||
@ -238,12 +240,13 @@ class Bilibili:
|
||||
# {"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}?'
|
||||
@ -263,10 +266,11 @@ class Bilibili:
|
||||
)
|
||||
if r.status_code != 200:
|
||||
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_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}?'
|
||||
@ -282,6 +286,7 @@ class Bilibili:
|
||||
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)
|
||||
|
||||
@ -314,6 +319,7 @@ class Bilibili:
|
||||
"""
|
||||
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,
|
||||
@ -330,21 +336,21 @@ class Bilibili:
|
||||
"order_id": 0,
|
||||
"videos": self.videos}
|
||||
)
|
||||
print(r.text)
|
||||
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)
|
||||
print("RELOAD Success")
|
||||
Common.appendUploadStatus("RELOAD SUCCESS")
|
||||
except:
|
||||
print("RELOAD Failed")
|
||||
Common.appendUploadStatus("RELOAD Failed")
|
||||
self.videos = []
|
||||
__f.close()
|
||||
os.remove("uploaded.json")
|
||||
else:
|
||||
print("RELOAD Failed")
|
||||
Common.appendUploadStatus("RELOAD Failed")
|
||||
self.videos = []
|
||||
|
||||
def clear(self):
|
||||
|
@ -2,71 +2,14 @@ import shutil
|
||||
import sys
|
||||
import time
|
||||
from datetime import datetime
|
||||
import queue
|
||||
import threading
|
||||
from config import config
|
||||
from api import XiGuaLiveApi
|
||||
from bilibili import *
|
||||
import Common
|
||||
import os
|
||||
import requests
|
||||
|
||||
q = queue.Queue()
|
||||
base_uri = ""
|
||||
isEncode = 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):
|
||||
@ -74,100 +17,129 @@ def download(url):
|
||||
path = datetime.strftime(datetime.now(), "%Y%m%d_%H%M.flv")
|
||||
p = requests.get(url, stream=True)
|
||||
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
|
||||
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")
|
||||
try:
|
||||
for t in p.iter_content(chunk_size=64 * 1024):
|
||||
if 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
|
||||
print("{} : Download Quiting".format(datetime.strftime(datetime.now(), "%y%m%d %H%M")))
|
||||
except Exception as e:
|
||||
print("{} : Download Quiting With Exception {}".format(datetime.strftime(datetime.now(), "%y%m%d %H%M"),
|
||||
e.__str__()))
|
||||
Common.appendError("Download >{}< With Exception {}".format(path, e.__str__()))
|
||||
f.close()
|
||||
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)
|
||||
return False
|
||||
eq.put(path)
|
||||
download(url)
|
||||
if Common.forceNotDownload:
|
||||
Common.modifyLastDownloadStatus("设置了不下载,所以[{}]不会下载".format(path))
|
||||
return
|
||||
else:
|
||||
Common.encodeQueue.put(path)
|
||||
download(url)
|
||||
|
||||
|
||||
def encode():
|
||||
global isEncode
|
||||
Common.appendEncodeStatus("Encode Daemon Starting")
|
||||
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):
|
||||
isEncode = True
|
||||
os.system("ffmpeg -i {} -c:v copy -c:a copy -f mp4 {}".format(i, i[:13] + ".mp4"))
|
||||
uq.put(i[:13] + ".mp4")
|
||||
if config["mv"]:
|
||||
shutil.move(i, config["mtd"])
|
||||
elif config["del"]:
|
||||
os.remove(i)
|
||||
isEncode = False
|
||||
if os.path.getsize(i) < 8 * 1024 * 1024:
|
||||
Common.appendEncodeStatus("Encoded File >{}< is too small, will ignore it".format(i))
|
||||
continue
|
||||
Common.appendEncodeStatus("Encoding >{}< Start".format(i))
|
||||
os.system("ffmpeg -i {} -c:v copy -c:a copy -f mp4 {} -y".format(i, i[:13] + ".mp4"))
|
||||
Common.uploadQueue.put(i[:13] + ".mp4")
|
||||
Common.modifyLastEncodeStatus("Encode >{}< Finished".format(i))
|
||||
|
||||
|
||||
def upload(date=datetime.strftime(datetime.now(), "%Y_%m_%d")):
|
||||
print("{} : Upload Daemon Starting".format(datetime.strftime(datetime.now(), "%y%m%d %H%M")))
|
||||
i = uq.get()
|
||||
Common.appendUploadStatus("Upload Daemon Starting")
|
||||
i = Common.uploadQueue.get()
|
||||
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):
|
||||
print("{} : Upload Daemon Receive Command {}"
|
||||
.format(datetime.strftime(datetime.now(), "%y%m%d %H%M"), i))
|
||||
if i is True:
|
||||
print("自动投稿中,请稍后")
|
||||
b.finishUpload(config["t_t"].format(date), 17, config["tag"], config["des"],
|
||||
source=config["src"], no_reprint=0)
|
||||
b.finishUpload(Common.config["t_t"].format(date), 17, Common.config["tag"], Common.config["des"],
|
||||
source=Common.config["src"], no_reprint=0)
|
||||
b.clear()
|
||||
break
|
||||
print("{} : Upload {}".format(datetime.strftime(datetime.now(), "%y%m%d %H%M"), i))
|
||||
if not os.path.exists(i):
|
||||
print("{} : Upload File Not Exist {}".format(datetime.strftime(datetime.now(), "%y%m%d %H%M"), i))
|
||||
i = uq.get()
|
||||
Common.appendError("Upload File Not Exist {}".format(i))
|
||||
i = Common.uploadQueue.get()
|
||||
continue
|
||||
try:
|
||||
b.preUpload(VideoPart(i, os.path.basename(i)))
|
||||
except:
|
||||
except Exception as e:
|
||||
Common.appendError(e.__str__())
|
||||
continue
|
||||
os.remove(i)
|
||||
i = uq.get()
|
||||
|
||||
print("{} : Upload Daemon Quiting".format(datetime.strftime(datetime.now(), "%y%m%d %H%M")))
|
||||
if not Common.forceNotEncode:
|
||||
os.remove(i)
|
||||
i = Common.uploadQueue.get()
|
||||
Common.appendUploadStatus("Upload Daemon Quiting")
|
||||
|
||||
|
||||
b = Bilibili()
|
||||
b.login(config["b_u"], config["b_p"])
|
||||
b.login(Common.config["b_u"], Common.config["b_p"])
|
||||
|
||||
if __name__ == "__main__":
|
||||
name = config["l_u"]
|
||||
print("西瓜直播录播助手 by JerryYan")
|
||||
api = downloader(name)
|
||||
print("进入", api.roomLiver, "的直播间")
|
||||
if not api.isValidRoom:
|
||||
input("房间不存在")
|
||||
sys.exit()
|
||||
print("=" * 30)
|
||||
d = datetime.strftime(datetime.now(), "%Y_%m_%d")
|
||||
et = threading.Thread(target=encode, args=())
|
||||
et.setDaemon(True)
|
||||
et.start()
|
||||
|
||||
|
||||
def run():
|
||||
global isEncode, isDownload, et
|
||||
Common.refreshDownloader()
|
||||
if not Common.api.isValidRoom:
|
||||
Common.appendError("[{}]房间未找到".format(Common.config["l_u"]))
|
||||
return
|
||||
d = None
|
||||
t = threading.Thread(target=download)
|
||||
ut = threading.Thread(target=upload, args=(d,))
|
||||
et = threading.Thread(target=encode, args=())
|
||||
et.setDaemon(True)
|
||||
et.start()
|
||||
_count = 0
|
||||
_count_error = 0
|
||||
while True:
|
||||
if api.isLive:
|
||||
if Common.api.isLive and not Common.forceNotBroadcasting:
|
||||
if d is None:
|
||||
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
|
||||
_preT = api.playlist
|
||||
_preT = Common.api.playlist
|
||||
if not _preT:
|
||||
Common.api.updRoomInfo()
|
||||
continue
|
||||
t = threading.Thread(target=download, args=(_preT,))
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
@ -181,31 +153,45 @@ if __name__ == "__main__":
|
||||
et.start()
|
||||
if _count % 15 == 0:
|
||||
try:
|
||||
api.updRoomInfo()
|
||||
Common.api.updRoomInfo()
|
||||
_count = 0
|
||||
_count_error = 0
|
||||
except Exception as e:
|
||||
print(e.__str__())
|
||||
Common.appendError(e.__str__())
|
||||
time.sleep(20)
|
||||
_count_error += 1
|
||||
continue
|
||||
if _count_error > 15:
|
||||
api.isLive = False
|
||||
Common.api.isLive = False
|
||||
_count += 1
|
||||
time.sleep(20)
|
||||
else:
|
||||
if d is not None:
|
||||
d = None
|
||||
if not isEncode and not isDownload:
|
||||
uq.put(True)
|
||||
Common.uploadQueue.put(True)
|
||||
isEncode = True
|
||||
isDownload = True
|
||||
del config
|
||||
from config import config
|
||||
# print("主播未开播,等待1分钟后重试")
|
||||
time.sleep(60)
|
||||
try:
|
||||
api.updRoomInfo()
|
||||
Common.api.updRoomInfo()
|
||||
_count_error = 0
|
||||
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
26
static/device.js
Normal 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
57
static/index.js
Normal 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
29
templates/device.html
Normal 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
26
templates/files.html
Normal 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
13
templates/head.html
Normal 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
78
templates/index.html
Normal 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>
|
Reference in New Issue
Block a user