LIVE_BUVID bool//cookies含LIVE_BUVID
)
+var (
+ Stream_url string//直播Web服务
+)
+
//消息队列
type Danmu_Main_mq_item struct {
Class string
g "github.com/qydysky/part/get"
web "github.com/qydysky/part/web"
reqf "github.com/qydysky/part/reqf"
+ limit "github.com/qydysky/part/limit"
funcCtrl "github.com/qydysky/part/funcCtrl"
uuid "github.com/gofrs/uuid"
)
var apilog = c.Log.Base(`api`)
-var api_limit = p.Limit(1,2000,30000)//频率限制1次/2s,最大等待时间30s
+var api_limit = limit.New(1,2000,30000)//频率限制1次/2s,最大等待时间30s
func Get(key string) {
apilog := apilog.Base_add(`Get`).L(`T: `,key)
}
cmdlog.L(`I: `, `舰长数:`, c.GuardNum)
cmdlog.L(`I: `, `分区排行:`, c.Note, `人气:`, c.Renqi)
+ if c.Stream_url != ""{cmdlog.L(`I: `, `直播Web服务:`, c.Stream_url)}
+
continue
}
{//弹幕发送
})
}
- w := web.New(&http.Server{})//新建web实例
+ w := web.New(&http.Server{
+ Addr: "127.0.0.1:"+strconv.Itoa(p.Sys().GetFreePort()),
+ })//新建web实例
w.Handle(map[string]func(http.ResponseWriter,*http.Request){//路径处理函数
`/`:func(w http.ResponseWriter,r *http.Request){
var path string = r.URL.Path[1:]
### 说明
本项目使用github action自动构建,构建过程详见[yml](https://github.com/qydysky/bili_danmu/blob/master/.github/workflows/go.yml)
+#### 局域网转直播流服务
+启动Web流服务,为下载的直播流提供局域网内的流服务。
+
+在`demo/config/config_K_v.json`中可找到配置项,默认开启。
+
+```
+ "直播保存位置Web服务":true,
+```
+
+开启之后,启动会显示服务地址,在局域网内打开网址可以取得当前直播流(`dtmp`结尾)的flv流地址。可以在其他设备进行网络串流观看(如安卓mx player)。通过对获取flv直播流进行修改,使得无论何时打开局域网串流,播放的进度与当前直播时刻相同。服务地址也可通过命令行` room`查看。
+
+```
+I: 2021/04/13 20:07:45 命令行操作 [直播Web服务: http://192.168.31.245:38259]
+```
+
+
#### 命令行操作
在准备动作完成(`T: 2021/03/06 16:22:39 命令行操作 [回车查看帮助]`)后,输入回车将显示帮助
```
- 查看房间信息
```
room
-I: 2021/03/08 01:06:53 命令行操作 [当前直播间信息]
-I: 2021/03/08 01:06:53 命令行操作 [C酱です 213电竞俱乐部开业了! 直播中]
-I: 2021/03/08 01:06:53 命令行操作 [已直播时长: 07:56:53]
-I: 2021/03/08 01:06:53 命令行操作 [营收: ¥0.00]
-I: 2021/03/08 01:06:53 命令行操作 [舰长数: 1427]
-I: 2021/03/08 01:06:53 命令行操作 [分区排行: 单机游戏 19 人气: 1321956]
+I: 2021/04/13 20:04:56 命令行操作 [当前直播间信息]
+I: 2021/04/13 20:04:56 命令行操作 [哔哩哔哩英雄联盟赛事 【直播】EDG vs RNG 直播中]
+I: 2021/04/13 20:04:56 命令行操作 [已直播时长: 244:02:58]
+I: 2021/04/13 20:04:56 命令行操作 [营收: ¥3193.10]
+I: 2021/04/13 20:04:56 命令行操作 [舰长数: 16]
+I: 2021/04/13 20:04:56 命令行操作 [分区排行: 50+ 人气: 41802746]
+I: 2021/04/13 20:04:56 命令行操作 [直播Web服务: http://192.168.31.245:38259]
```
#### cookie加密
保护cookie.txt
"time"
"os/exec"
"path/filepath"
+ "net/http"
+ "context"
c "github.com/qydysky/bili_danmu/CV"
F "github.com/qydysky/bili_danmu/F"
funcCtrl "github.com/qydysky/part/funcCtrl"
msgq "github.com/qydysky/part/msgq"
reqf "github.com/qydysky/part/reqf"
+ web "github.com/qydysky/part/web"
b "github.com/qydysky/part/buf"
s "github.com/qydysky/part/signal"
+ limit "github.com/qydysky/part/limit"
"github.com/christopher-dG/go-obs-websocket"
)
type Lessdanmu struct {
buf []string
+ limit *limit.Limit
+ max_num int
+ threshold float32
+
+ sync.RWMutex
}
var lessdanmu = Lessdanmu{
+ threshold:0.7,
}
-func Lessdanmuf(s string, bufsize int) float32 {
- if !IsOn("相似弹幕忽略") {return 0}
- if len(lessdanmu.buf) < bufsize {
+func init() {
+ if max_num,ok := c.K_v.LoadV(`每10秒显示弹幕数`).(float64);ok && int(max_num) >= 1 {
+ flog.Base_add(`更少弹幕`).L(`T: `,`每10秒弹幕数:`,int(max_num))
+ lessdanmu.max_num = int(max_num)
+ lessdanmu.limit = limit.New(int(max_num),10000,0)
+ go func(){
+ //等待启动
+ for lessdanmu.limit.PTK() == lessdanmu.max_num {
+ time.Sleep(time.Second*3)
+ }
+
+ for {
+ time.Sleep(time.Second*10)
+
+ lessdanmu.Lock()
+ if ptk := lessdanmu.limit.PTK();ptk == lessdanmu.max_num {
+ if lessdanmu.threshold > 0.71 {
+ lessdanmu.threshold -= 0.01
+ }
+ } else if ptk == 0 {
+ if lessdanmu.threshold < 0.99 {
+ lessdanmu.threshold += 0.01
+ }
+ }
+ lessdanmu.Unlock()
+ }
+ }()
+ }
+}
+
+func Lessdanmuf(s string) (show bool) {
+ if !IsOn("相似弹幕忽略") {return true}
+ if len(lessdanmu.buf) < 20 {
lessdanmu.buf = append(lessdanmu.buf, s)
- return 0
+ return true
}
o := cross(s, lessdanmu.buf)
- if o == 1 {return 1}//完全无用
+ if o == 1 {return false}//完全无用
+
Jiezouf(lessdanmu.buf)
lessdanmu.buf = append(lessdanmu.buf[1:], s)
- return o
+ lessdanmu.RLock()
+ show = o > lessdanmu.threshold
+ lessdanmu.RUnlock()
+
+ if show && lessdanmu.max_num > 0 {
+ lessdanmu.limit.TO()
+ }
+ return
}
/*
} else {
flog.L(`T: `,`完成`)
}
+}
+
+//直播保存位置Web服务
+func init() {
+ if v,ok := c.K_v.LoadV(`直播保存位置Web服务`).(bool);ok && v {
+ base_dir := ""
+ if path,ok := c.K_v.LoadV(`直播流保存位置`).(string);ok && path !="" {
+ if path,err := filepath.Abs(path);err == nil{
+ base_dir = path+"/"
+ }
+ }
+ s := web.New(&http.Server{
+ Addr: "0.0.0.0:"+strconv.Itoa(p.Sys().GetFreePort()),
+ })
+ s.Handle(map[string]func(http.ResponseWriter,*http.Request){
+ `/`:func(w http.ResponseWriter,r *http.Request){
+ var path string = r.URL.Path[1:]
+ if path == `` {
+ http.FileServer(http.Dir(base_dir)).ServeHTTP(w,r)
+ } else {
+ path = base_dir+path
+
+ if !p.Checkfile().IsExist(path) {
+ w.WriteHeader(404)
+ return
+ }
+
+ w.WriteHeader(200)
+ if f, ok := w.(http.Flusher); ok {
+ f.Flush()
+ }
+
+ byteC := make(chan []byte,1024*1024)
+ cancel := make(chan struct{})
+ defer close(cancel)
+ go func() {
+ if e := Stream(path,byteC,cancel);e != nil {
+ flog.Base_add(`直播Web服务`).L(`T: `,`E: `,e);
+ return
+ }
+ }()
+
+ for {
+ buf := <- byteC
+ if len(buf) == 0 {break}
+
+ if _,err := w.Write(buf);err != nil {
+ flog.Base_add(`直播Web服务`).L(`T: `,`E: `,err);
+ break
+ }
+ }
+ }
+ },
+ `/exit`:func(w http.ResponseWriter,r *http.Request){
+ s.Server.Shutdown(context.Background())
+ },
+ })
+ host := p.Sys().GetIntranetIp()
+ c.Stream_url = strings.Replace(`http://`+s.Server.Addr,`0.0.0.0`,host,-1)
+ flog.Base_add(`直播Web服务`).L(`I: `,`启动于`,c.Stream_url)
+ }
}
\ No newline at end of file
|ws_msg/|弹幕服务器传回的Msg类型json的golang struct表述|
|0Init.go|最初始载入配置|
|F.go|附加功能|
+|flvDecode.go|直播流转局域网直播流|
|Heartbeat.go|人气数据分派|
|Msg.go|消息数据分派|
|Reply.go|分派数据处理|
func Reply(b []byte) {
reply_log := reply_log.Base_add(`返回分派`)
+ if len(b) <= c.WS_PACKAGE_HEADER_TOTAL_LENGTH {reply_log.L(`W: `,"包缺损");return}
+
head := F.HeadChe(b[:c.WS_PACKAGE_HEADER_TOTAL_LENGTH])
if int(head.PackL) > len(b) {reply_log.L(`E: `,"包缺损");return}
msg := m[0]
msglog := msglog.Log_show_control(false)
{//附加功能 更少弹幕
- if Lessdanmuf(msg, 20) > 0.7 {//与前20条弹幕重复的字数占比度>0.7的屏蔽
+ if !Lessdanmuf(msg) {
if auth != nil {msglog.L(`I: `, auth, ":", msg)}
return
}
--- /dev/null
+package reply
+
+import (
+ "os"
+ "bytes"
+ "time"
+ "errors"
+
+ c "github.com/qydysky/bili_danmu/CV"
+ F "github.com/qydysky/bili_danmu/F"
+)
+
+const (
+ flv_header_size = 9
+ tag_header_size = 11
+ previou_tag_size = 4
+
+ video_tag = byte(0x09)
+ audio_tag = byte(0x08)
+ script_tag = byte(0x12)
+
+ //custom define
+ eof_tag = byte(0x00)
+ copy_buf_size = 1024*1024
+)
+
+var (
+ flv_header_sign = []byte{0x46,0x4c,0x56}
+ flvlog = c.Log.Base(`flv解码`)
+)
+
+func Stream(path string,streamChan chan []byte,cancel chan struct{}) (error) {
+ //
+ defer flvlog.L(`T: `,`退出`)
+ //file
+ f,err := os.OpenFile(path,os.O_RDONLY,0644)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+ defer close(streamChan)
+
+ //get flv header(9byte) + FirstTagSize(4byte)
+ {
+ f.Seek(0,0)
+ buf := make([]byte, flv_header_size+previou_tag_size)
+ if _,err := f.Read(buf);err != nil {return err}
+ if !bytes.Contains(buf,flv_header_sign) {return errors.New(`no flv`)}
+ streamChan <- buf
+ }
+
+ //get tag func
+ var getTag = func(f *os.File)(tag byte,offset int64,buf_p *[]byte,data_p *[]byte){
+ buf := make([]byte, tag_header_size)
+ if _,err := f.Read(buf);err != nil {tag = eof_tag;return}
+ tag = buf[0]
+ size := F.Btoi32(append([]byte{0x00},buf[1:4]...),0)
+
+ data := make([]byte, size+previou_tag_size)
+ if _,err := f.Read(data);err != nil {tag = eof_tag;return}
+
+ offset,_ = f.Seek(0,1)
+ offset -= tag_header_size+int64(size)+previou_tag_size
+ return tag,offset,&buf,&data
+ }
+
+ //get first video and audio tag
+ //find last_keyframe_video_offset
+ var last_keyframe_video_offset int64
+ first_video_tag,first_audio_tag := false,false
+ for {
+ tag,offset,buf_p,data_p := getTag(f)
+ if tag == script_tag {
+ streamChan <- *buf_p
+ streamChan <- *data_p
+ } else if tag == video_tag {
+ if !first_video_tag {
+ first_video_tag = true
+ streamChan <- *buf_p
+ streamChan <- *data_p
+ }
+
+ if (*data_p)[0] & 0xf0 == 0x10 {
+ last_keyframe_video_offset = offset
+ }
+ } else if tag == audio_tag {
+ if !first_audio_tag {
+ first_audio_tag = true
+ streamChan <- *buf_p
+ streamChan <- *data_p
+ }
+ } else {//eof_tag
+ break;
+ }
+ }
+
+ //seed to last tag
+ f.Seek(last_keyframe_video_offset,0)
+
+ //copy
+ {
+ buf := make([]byte, copy_buf_size)
+ eof_wait_turn := 3
+ for {
+
+ //退出
+ select {
+ case <-cancel:return nil;
+ default:;
+ }
+
+ size,err := f.Read(buf)
+ if err != nil {
+ if err.Error() != `EOF` {
+ return err
+ }
+ if eof_wait_turn < 0 {break}
+ eof_wait_turn -= 1
+ }
+
+ if size > 0 {
+ streamChan <- buf[:size]
+ }
+
+ if eof_wait_turn > 0 {time.Sleep(time.Second*3)}
+ }
+ }
+
+ return nil
+}
msgq "github.com/qydysky/part/msgq"
s "github.com/qydysky/part/buf"
reqf "github.com/qydysky/part/reqf"
+ limit "github.com/qydysky/part/limit"
)
var tts_setting = map[string]string{
}
var tts_List = make(chan interface{},20)
-var tts_limit = p.Limit(1,5000,15000)//频率限制1次/5s,最大等待时间15s
+var tts_limit = limit.New(1,5000,15000)//频率限制1次/5s,最大等待时间15s
var tts_log = c.Log.Base_add(`TTS`)
p "github.com/qydysky/part"
reqf "github.com/qydysky/part/reqf"
+ limit "github.com/qydysky/part/limit"
)
//每5s一个令牌,最多等20秒
-var danmu_s_limit = p.Limit(1, 5000, 20000)
+var danmu_s_limit = limit.New(1, 5000, 20000)
//弹幕发送
func Danmu_s(msg string, roomid int) {
c "github.com/qydysky/bili_danmu/CV"
- p "github.com/qydysky/part"
reqf "github.com/qydysky/part/reqf"
+ limit "github.com/qydysky/part/limit"
)
//每2s一个令牌,最多等10秒
-var gift_limit = p.Limit(1, 2000, 10000)
+var gift_limit = limit.New(1, 2000, 10000)
func Send_gift(gift_id,bag_id,gift_num int) {
log := c.Log.Base_add(`发送礼物`)
p "github.com/qydysky/part"
reqf "github.com/qydysky/part/reqf"
+ limit "github.com/qydysky/part/limit"
uuid "github.com/gofrs/uuid"
)
}
//每5s一个令牌,最多等10秒
-var pm_limit = p.Limit(1, 5000, 10000)
+var pm_limit = limit.New(1, 5000, 10000)
func Send_pm(uid int, msg string) error {
if msg == `` || uid == 0 {
"直播流清晰度-help": "清晰度可选-1:不保存 0:默认 10000:原画 800:4K 401:蓝光(杜比) 400:蓝光 250:超清 150:高清 80:流畅,无提供所选清晰度时,使用低一档清晰度",
"flv直播流清晰度": 150,
"直播流保存位置": "./live",
+ "直播保存位置Web服务":true,
"ass-help": "只有保存直播流时才考虑生成ass",
"生成Ass弹幕": true,
"弹幕处理": "",
"弹幕合并": true,
"相似弹幕忽略": true,
+ "每10秒显示弹幕数-help": "为0时禁用,相似弹幕忽略需设为true",
+ "每10秒显示弹幕数": 14,
"精简弹幕": true,
"弹幕机": "",
"反射弹幕机": true,
github.com/miekg/dns v1.1.41 // indirect
github.com/mitchellh/mapstructure v1.4.1 // indirect
github.com/qydysky/bili_danmu v0.5.7
- github.com/qydysky/part v0.5.4 // indirect
+ github.com/qydysky/part v0.5.9 // indirect
github.com/shirou/gopsutil v3.21.3+incompatible // indirect
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect
github.com/qydysky/part v0.5.3/go.mod h1:43opuciW71sZvOR67kye50jgMDSDrn/t6+LefNdlXPg=
github.com/qydysky/part v0.5.4 h1:o7nslWdOM94wGEGGPWK0uscx8NR3UlJpnKTQCQxBPfQ=
github.com/qydysky/part v0.5.4/go.mod h1:43opuciW71sZvOR67kye50jgMDSDrn/t6+LefNdlXPg=
+github.com/qydysky/part v0.5.5 h1:FDhG5s3tmVBm3tzN0p7cwFPkAOH6Z9zl91zznqNS/Yk=
+github.com/qydysky/part v0.5.5/go.mod h1:43opuciW71sZvOR67kye50jgMDSDrn/t6+LefNdlXPg=
+github.com/qydysky/part v0.5.6 h1:EDzYYb9H9rHB0uwnOeDrhKU/7VFjxk1vS6Dh+x4ZgcM=
+github.com/qydysky/part v0.5.6/go.mod h1:43opuciW71sZvOR67kye50jgMDSDrn/t6+LefNdlXPg=
+github.com/qydysky/part v0.5.7 h1:xHwEZIV95I9N6rJqvfDGbn6ivJec6mk7RpMYnLRxlV0=
+github.com/qydysky/part v0.5.7/go.mod h1:43opuciW71sZvOR67kye50jgMDSDrn/t6+LefNdlXPg=
+github.com/qydysky/part v0.5.8 h1:sEGsa9ftwHSCu1yc9AjG2PCVE/kLHLkUpYTVYWJvV6Y=
+github.com/qydysky/part v0.5.8/go.mod h1:43opuciW71sZvOR67kye50jgMDSDrn/t6+LefNdlXPg=
+github.com/qydysky/part v0.5.9 h1:XZxBs6bBVQCKH48791X8ebrReY50aGw9IFCdaM1QhGA=
+github.com/qydysky/part v0.5.9/go.mod h1:43opuciW71sZvOR67kye50jgMDSDrn/t6+LefNdlXPg=
github.com/qydysky/part/msgq v0.0.0-20201213031129-ca3253dc72ad h1:Jtzf509lQrkUMGTV0Sc6IDCAiR1VrBcHrIban7hpye4=
github.com/qydysky/part/msgq v0.0.0-20201213031129-ca3253dc72ad/go.mod h1:w32TkJNVtTJd4LOS09cq+4uYG6itcN2vsqw+slp44Rg=
github.com/qydysky/part/msgq v0.0.0-20201213120821-f36e49c32bba h1:1ew9dRpc0Rux0WkWeT/4AE15ynYWmL2D7onJEJIFOB8=