]> 127.0.0.1 Git - bili_danmu/.git/commitdiff
Improve 结束录制时保存表情包 (#186)
authorqydysky <qydysky@foxmail.com>
Tue, 8 Apr 2025 12:56:26 +0000 (20:56 +0800)
committerGitHub <noreply@github.com>
Tue, 8 Apr 2025 12:56:26 +0000 (20:56 +0800)
* Improve 结束录制时保存表情包

* Improve 优化

* Improve 添加说明

* Fix golangci-lint check

* Improve 优化

README.md
Reply/F.go
Reply/F/comp.go
Reply/F/danmuEmotes/danmuEmotes.go
go.mod
go.sum

index d593b57d4fc41a929b974968ce44a7d905e2afb3..4ec80d9aa1d3de7c7056654be895ee2f65d5651d 100644 (file)
--- a/README.md
+++ b/README.md
@@ -304,9 +304,7 @@ curl -s http://{主机名}:11000/ip/ | awk '/240:?/'
 #### 直播回放显示表情
 配置文件中添加配置项`弹幕表情`(>v0.14.9)。默认为true,当为true时,将会保存弹幕中的表情png到emots目录下,并在回放时显示表情。
 
-为了能顺利保存,会将某些字符进行转换(<=v0.14.18),如:[dog?]=>[dog?].png。
-
-但之后(>v0.14.18)存储通过md5作为文件名,如:[dog?]=>md5("[dog?]")+".png"=>0c8427bdb9854d85e9cfe59c45d70583.png
+为了能顺利保存,存储通过md5作为文件名,如:[dog?]=>md5("[dog?]")+".png"=>0c8427bdb9854d85e9cfe59c45d70583.png
 
 从而避免对原表情产生修改,且避免不同环境对文件名支持的差异。
 
@@ -314,6 +312,10 @@ curl -s http://{主机名}:11000/ip/ | awk '/240:?/'
 
 保存的表情可以通过`http://{Web服务地址}{直播Web服务路径}emots/{md5}.png`获取。
 
+在(>v0.16.6)中,在录制结束时,本场录制出现的表情将会从emots目录复制到录制文件夹下的`emotes.zip`中。
+当请求`http://{Web服务地址}{直播Web服务路径}emots/{md5}.png`时,如请求头`Referer`中的`ref`url参数为有效的录播文件夹名,并且该文件夹下有`emotes.zip`。
+则在回放中使用`emotes.zip`中的表情,这确保了录播文件夹迁移到其他环境时,即使emots目录没有对应表情,也可以正常回放表情。
+
 #### 直播流停用服务器
 配置文件中添加配置项`直播流停用服务器`(>v0.14.3)。默认为空,编写正则字符串,当获取到的服务器链接与字符串匹配时,将会停用。
 
index de06fab3271fe9385037b3fc0438d12b7117ac32..60c2c8b77e4ec7499faca63128e9ced3ae0d8639 100644 (file)
@@ -7,6 +7,7 @@ import (
        "errors"
        "fmt"
        "io"
+       "io/fs"
        "net"
        "net/http"
        "net/http/pprof"
@@ -961,19 +962,34 @@ func init() {
                                return
                        }
 
-                       f := file.New("emots/"+strings.TrimPrefix(r.URL.Path, spath+"emots/"), 0, true).CheckRoot("emots/")
-                       if !f.IsExist() {
-                               w.WriteHeader(http.StatusNotFound)
-                               return
+                       ref := ""
+                       if u, e := url.Parse(r.Header.Get("Referer")); e == nil {
+                               ref = u.Query().Get("ref")
+                               if ref != "now" {
+                                       if s, ok := c.C.K_v.LoadV(`直播流保存位置`).(string); ok && s != "" {
+                                               if strings.HasSuffix(s, "/") || strings.HasSuffix(s, "\\") {
+                                                       ref = s + ref + "/"
+                                               } else {
+                                                       ref = s + "/" + ref + "/"
+                                               }
+                                       }
+                               } else {
+                                       ref = ""
+                               }
                        }
+                       emoteDir := replyFunc.DanmuEmotes.GetEmotesDir(ref)
 
-                       // mod
-                       if info, e := f.Stat(); e == nil && pweb.NotModified(r, w, info.ModTime()) {
-                               return
+                       if f, e := emoteDir.Open(strings.TrimPrefix(r.URL.Path, spath+"emots/")); e != nil {
+                               if errors.Is(e, fs.ErrNotExist) {
+                                       w.WriteHeader(http.StatusNotFound)
+                               } else {
+                                       w.WriteHeader(http.StatusBadRequest)
+                               }
+                       } else if info, e := f.Stat(); e != nil {
+                               w.WriteHeader(http.StatusNotFound)
+                       } else if !pweb.NotModified(r, w, info.ModTime()) {
+                               _, _ = io.Copy(w, f)
                        }
-
-                       b, _ := f.ReadAll(humanize.KByte, humanize.MByte)
-                       _, _ = w.Write(b)
                })
 
                // 直播流播放器
@@ -1437,6 +1453,11 @@ func StartRecDanmu(ctx context.Context, filePath string) {
        // Ass
        replyFunc.Ass.ToAss(filePath)
 
+       // emots
+       if e := replyFunc.DanmuEmotes.PackEmotes(filePath); e != nil {
+               msglog.L(`E: `, e)
+       }
+
        Recoder.Stop()
 }
 
index 06cb920f0e2209ed32c9f699773c2865a52f67f9..2b923e50d25de409125e13bd624d73df6073b098 100644 (file)
@@ -2,6 +2,7 @@ package f
 
 import (
        "context"
+       "io/fs"
        "iter"
        "net/http"
        "time"
@@ -68,4 +69,6 @@ var DanmuEmotes = comp.Get[interface {
        Hashr(s string) (r string)
        SetLayerN(n int)
        IsErrNoEmote(e error) bool
+       PackEmotes(dir string) error
+       GetEmotesDir(dir string) fs.FS
 }](`danmuEmotes`)
index 4ad1720c212c8943ded2f3afb6cea9fb4f243a66..9a08fc9c77e153f657559607ee5a2bcc24161f2d 100644 (file)
@@ -1,11 +1,19 @@
 package danmuemotes
 
 import (
+       "archive/zip"
+       "bytes"
        "context"
        "encoding/json"
        "errors"
+       "io"
+       "io/fs"
+       "iter"
+       "regexp"
+       "strconv"
        "strings"
 
+       "github.com/dustin/go-humanize"
        c "github.com/qydysky/bili_danmu/CV"
        comp "github.com/qydysky/part/component2"
        file "github.com/qydysky/part/file"
@@ -23,6 +31,8 @@ type TargetInterface interface {
        Hashr(s string) (r string)
        SetLayerN(n int)
        IsErrNoEmote(e error) bool
+       PackEmotes(dir string) error
+       GetEmotesDir(dir string) fs.FS
 }
 
 func init() {
@@ -165,3 +175,94 @@ func (t *danmuEmotes) Hashr(s string) (r string) {
        }
        return string(rr)
 }
+
+func (t *danmuEmotes) PackEmotes(dir string) error {
+       var (
+               w    *zip.Writer
+               set  = make(map[string]struct{})
+               r, _ = regexp.Compile(`\[.*?\]`)
+       )
+
+       for line := range loadCsv(dir, "0.csv") {
+               for _, v := range r.FindAllString(line.Text, -1) {
+                       key := t.Hashr(v)
+                       if _, ok := set[key]; ok {
+                               continue
+                       } else {
+                               set[key] = struct{}{}
+                       }
+
+                       f := file.New(t.Dir+key+".png", 0, false)
+                       if f.IsExist() {
+                               if w == nil {
+                                       f := file.Open(dir + "emotes.zip")
+                                       if f.IsExist() {
+                                               _ = f.Delete()
+                                       }
+                                       w = zip.NewWriter(f.File())
+                                       defer w.Close()
+                               }
+                               if iw, e := w.Create(key + ".png"); e != nil {
+                                       return e
+                               } else if _, e := io.Copy(iw, f); e != nil {
+                                       return e
+                               }
+                       }
+               }
+       }
+       return nil
+}
+
+func (t *danmuEmotes) GetEmotesDir(dir string) fs.FS {
+       if dir != "" && file.IsExist(dir+"emotes.zip") {
+               if rc, e := zip.OpenReader(dir + "emotes.zip"); e == nil {
+                       return rc
+               }
+       }
+       return file.DirFS(t.Dir)
+}
+
+func loadCsv(savePath string, filename ...string) iter.Seq[Data] {
+       return func(yield func(Data) bool) {
+               csvf := file.New(savePath+append(filename, "0.csv")[0], 0, false)
+               defer csvf.Close()
+
+               if !csvf.IsExist() {
+                       return
+               }
+
+               var data = Data{}
+               for i := 0; true; i += 1 {
+                       if line, e := csvf.ReadUntil([]byte{'\n'}, humanize.KByte, humanize.MByte); len(line) != 0 {
+                               lined := bytes.SplitN(line, []byte{','}, 3)
+                               if len(lined) == 3 {
+                                       if t, e := strconv.ParseFloat(string(lined[0]), 64); e == nil {
+                                               if e := json.Unmarshal(lined[2], &data); e == nil {
+                                                       data.Time = t
+                                                       if data.Style.Color == "" {
+                                                               data.Style.Color = "#FFFFFF"
+                                                       }
+                                                       if !yield(data) {
+                                                               return
+                                                       }
+                                               }
+                                       }
+                               }
+                       } else if e != nil {
+                               break
+                       }
+               }
+       }
+}
+
+type DataStyle struct {
+       Color  string `json:"color"`
+       Border bool   `json:"border"`
+       Mode   int    `json:"mode"`
+}
+
+type Data struct {
+       Text  string    `json:"text"`
+       Style DataStyle `json:"style"`
+       Time  float64   `json:"time"`
+}
diff --git a/go.mod b/go.mod
index 18d2b8faeb749e985acddaa7c1daf53edf623c24..8b6d52c2a826ae6cc8648be8525f8f929817ff74 100644 (file)
--- a/go.mod
+++ b/go.mod
@@ -5,7 +5,7 @@ go 1.24
 require (
        github.com/gotk3/gotk3 v0.6.4
        github.com/mdp/qrterminal/v3 v3.2.0
-       github.com/qydysky/part v0.28.20250330170611
+       github.com/qydysky/part v0.28.20250406180726
        github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
        github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
        golang.org/x/text v0.23.0 // indirect
diff --git a/go.sum b/go.sum
index add27d58288273dc9f39ed7c58c8679d199203e6..e8beba5311328e800ca53d3f71b683880c7e7aa6 100644 (file)
--- a/go.sum
+++ b/go.sum
@@ -46,8 +46,8 @@ github.com/qydysky/biliApi v0.0.0-20250406112014-bf8c070170f6 h1:eWklz9YhqcLnJeH
 github.com/qydysky/biliApi v0.0.0-20250406112014-bf8c070170f6/go.mod h1:1FbgCj+aOwIvuRRuX/l5uTLb3JIwWyJSa0uEfwpYV/8=
 github.com/qydysky/brotli v0.0.0-20240828134800-e9913a6e7ed9 h1:k451T+bpsLr+Dq9Ujo+Qtx0iomRA1XXS5ttlEojvfuQ=
 github.com/qydysky/brotli v0.0.0-20240828134800-e9913a6e7ed9/go.mod h1:cI8/gy/wjy2Eb+p2IUj2ZuDnC8R5Vrx3O0VMPvMvphA=
-github.com/qydysky/part v0.28.20250330170611 h1:8ll4oVALYXi0wFce12r8BkYRdlw8U50VZs7FI6AZTog=
-github.com/qydysky/part v0.28.20250330170611/go.mod h1:RHYTy8EbqCP6OioVf6BkvFcfWLNO0S220zl0DDlY84Y=
+github.com/qydysky/part v0.28.20250406180726 h1:uCHzGPpH3USZR7tilD5H0h07DjZNMF4XU2K+U+Q7TIc=
+github.com/qydysky/part v0.28.20250406180726/go.mod h1:RHYTy8EbqCP6OioVf6BkvFcfWLNO0S220zl0DDlY84Y=
 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
 github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=