From 90cb132729c9e25c335e8cc5760a54b32751cab2 Mon Sep 17 00:00:00 2001 From: qydysky Date: Tue, 13 Apr 2021 20:22:42 +0800 Subject: [PATCH] =?utf8?q?=E5=B1=80=E5=9F=9F=E7=BD=91=E8=BD=AC=E7=9B=B4?= =?utf8?q?=E6=92=AD=E6=9C=8D=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- CV/Var.go | 4 ++ F/api.go | 3 +- F/cmd.go | 2 + F/xinxin.go | 4 +- README.md | 29 ++++++-- Reply/F.go | 121 +++++++++++++++++++++++++++++++-- Reply/README.md | 1 + Reply/Reply.go | 4 +- Reply/flvDecode.go | 130 ++++++++++++++++++++++++++++++++++++ Reply/tts.go | 3 +- Send/Send.go | 3 +- Send/Send_gift.go | 4 +- Send/Send_pm.go | 3 +- demo/config/config_K_v.json | 3 + demo/go.mod | 2 +- demo/go.sum | 10 +++ 16 files changed, 305 insertions(+), 21 deletions(-) create mode 100644 Reply/flvDecode.go diff --git a/CV/Var.go b/CV/Var.go index de5384f..42f409a 100644 --- a/CV/Var.go +++ b/CV/Var.go @@ -35,6 +35,10 @@ var ( LIVE_BUVID bool//cookies含LIVE_BUVID ) +var ( + Stream_url string//直播Web服务 +) + //消息队列 type Danmu_Main_mq_item struct { Class string diff --git a/F/api.go b/F/api.go index 7be40cb..5db478a 100644 --- a/F/api.go +++ b/F/api.go @@ -17,6 +17,7 @@ import ( 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" @@ -26,7 +27,7 @@ import ( ) 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) diff --git a/F/cmd.go b/F/cmd.go index b536ffc..2dcd6a7 100644 --- a/F/cmd.go +++ b/F/cmd.go @@ -82,6 +82,8 @@ func Cmd() { } cmdlog.L(`I: `, `舰长数:`, c.GuardNum) cmdlog.L(`I: `, `分区排行:`, c.Note, `人气:`, c.Renqi) + if c.Stream_url != ""{cmdlog.L(`I: `, `直播Web服务:`, c.Stream_url)} + continue } {//弹幕发送 diff --git a/F/xinxin.go b/F/xinxin.go index e83456a..53050e5 100644 --- a/F/xinxin.go +++ b/F/xinxin.go @@ -114,7 +114,9 @@ func server() { }) } - 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:] diff --git a/README.md b/README.md index dbe7a36..3f4baed 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,22 @@ golang go version go1.15 linux/amd64 ### 说明 本项目使用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 命令行操作 [回车查看帮助]`)后,输入回车将显示帮助 ``` @@ -136,12 +152,13 @@ I: 2021/03/06 16:21:17 弹幕发送 [发送 1 至 7734200] - 查看房间信息 ``` 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 diff --git a/Reply/F.go b/Reply/F.go index 259930b..5e5e50a 100644 --- a/Reply/F.go +++ b/Reply/F.go @@ -9,6 +9,8 @@ import ( "time" "os/exec" "path/filepath" + "net/http" + "context" c "github.com/qydysky/bili_danmu/CV" F "github.com/qydysky/bili_danmu/F" @@ -18,8 +20,10 @@ import ( 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" ) @@ -618,24 +622,68 @@ func Autoskipf(s string) uint { 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 } /* @@ -893,4 +941,65 @@ func AutoSend_silver_gift() { } 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 diff --git a/Reply/README.md b/Reply/README.md index 7e40a9d..7ab39e1 100644 --- a/Reply/README.md +++ b/Reply/README.md @@ -8,6 +8,7 @@ |ws_msg/|弹幕服务器传回的Msg类型json的golang struct表述| |0Init.go|最初始载入配置| |F.go|附加功能| +|flvDecode.go|直播流转局域网直播流| |Heartbeat.go|人气数据分派| |Msg.go|消息数据分派| |Reply.go|分派数据处理| diff --git a/Reply/Reply.go b/Reply/Reply.go index f140007..c4efe13 100644 --- a/Reply/Reply.go +++ b/Reply/Reply.go @@ -25,6 +25,8 @@ var reply_log = c.Log.Base(`Reply`) 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} @@ -744,7 +746,7 @@ func Msg_showdanmu(auth interface{}, m ...string) { 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 } diff --git a/Reply/flvDecode.go b/Reply/flvDecode.go new file mode 100644 index 0000000..4a2ed33 --- /dev/null +++ b/Reply/flvDecode.go @@ -0,0 +1,130 @@ +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 +} diff --git a/Reply/tts.go b/Reply/tts.go index 3cd253d..edc6b0e 100644 --- a/Reply/tts.go +++ b/Reply/tts.go @@ -10,6 +10,7 @@ import ( 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{ @@ -19,7 +20,7 @@ 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`) diff --git a/Send/Send.go b/Send/Send.go index dd9d760..6fd863c 100644 --- a/Send/Send.go +++ b/Send/Send.go @@ -9,10 +9,11 @@ import ( 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) { diff --git a/Send/Send_gift.go b/Send/Send_gift.go index 338f0e8..0857093 100644 --- a/Send/Send_gift.go +++ b/Send/Send_gift.go @@ -8,12 +8,12 @@ import ( 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(`发送礼物`) diff --git a/Send/Send_pm.go b/Send/Send_pm.go index eca5e81..338ac0f 100644 --- a/Send/Send_pm.go +++ b/Send/Send_pm.go @@ -9,6 +9,7 @@ import ( p "github.com/qydysky/part" reqf "github.com/qydysky/part/reqf" + limit "github.com/qydysky/part/limit" uuid "github.com/gofrs/uuid" ) @@ -19,7 +20,7 @@ type Pm_item struct { } //每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 { diff --git a/demo/config/config_K_v.json b/demo/config/config_K_v.json index fb03596..2b0c7f0 100644 --- a/demo/config/config_K_v.json +++ b/demo/config/config_K_v.json @@ -46,11 +46,14 @@ "直播流清晰度-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, diff --git a/demo/go.mod b/demo/go.mod index 73f53b3..9dbd872 100644 --- a/demo/go.mod +++ b/demo/go.mod @@ -13,7 +13,7 @@ require ( 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 diff --git a/demo/go.sum b/demo/go.sum index 75ecaea..8b82e9d 100644 --- a/demo/go.sum +++ b/demo/go.sum @@ -257,6 +257,16 @@ github.com/qydysky/part v0.5.3 h1:bvNvXOdzZr0nd3p+VQDgvGzugwuvk+yNtO6oTD32trw= 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= -- 2.39.2