]> 127.0.0.1 Git - bili_danmu/.git/commitdiff
Fix 优化 (#208)
authorqydysky <qydysky@foxmail.com>
Sun, 18 May 2025 15:44:38 +0000 (23:44 +0800)
committerGitHub <noreply@github.com>
Sun, 18 May 2025 15:44:38 +0000 (23:44 +0800)
* Fix 优化

* Fix 优化

Reply/flvDecode.go
Reply/fmp4Decode.go
go.mod
go.sum

index 881f0f1893ab56272b800a0046d09eb3f8082603..76071d08aac4a17bc26f1829ed45039878636d61 100644 (file)
@@ -288,7 +288,6 @@ func (t *FlvDecoder) Cut(reader io.Reader, startT, duration time.Duration, w io.
 }
 
 func (t *FlvDecoder) CutSeed(reader io.Reader, startT, duration time.Duration, w io.Writer, seeker io.Seeker, getIndex func(seedTo time.Duration) (int64, error)) (err error) {
-       bufSize := humanize.KByte * 1100
        buf := make([]byte, humanize.KByte*500)
        buff := slice.New[byte]()
        over := false
@@ -310,14 +309,11 @@ func (t *FlvDecoder) CutSeed(reader io.Reader, startT, duration time.Duration, w
        }
 
        for c := 0; err == nil && !over; c++ {
-               if buff.Size() < bufSize {
-                       n, e := reader.Read(buf)
-                       if n == 0 && errors.Is(e, io.EOF) {
-                               return io.EOF
-                       }
-                       err = buff.Append(buf[:n])
-                       continue
+               n, e := reader.Read(buf)
+               if n == 0 && errors.Is(e, io.EOF) {
+                       return io.EOF
                }
+               err = buff.Append(buf[:n])
 
                if !t.init {
                        if frontBuf, dropOffset, e := t.InitFlv(buff.GetPureBuf()); e != nil {
@@ -325,8 +321,6 @@ func (t *FlvDecoder) CutSeed(reader io.Reader, startT, duration time.Duration, w
                        } else {
                                if dropOffset != 0 {
                                        _ = buff.RemoveFront(dropOffset)
-                               } else {
-                                       bufSize *= 2
                                }
                                if len(frontBuf) == 0 {
                                        continue
@@ -345,15 +339,17 @@ func (t *FlvDecoder) CutSeed(reader io.Reader, startT, duration time.Duration, w
                                }
                                seek = true
                                startTM = 0
-                               buff.Clear()
+                               buff.Reset()
                        }
-                       if dropOffset, e := t.oneF(buff.GetPureBuf(), wf); e != nil {
-                               return perrors.New(e.Error(), ActionOneFFlv)
-                       } else {
-                               if dropOffset != 0 {
-                                       _ = buff.RemoveFront(dropOffset)
+                       for {
+                               if dropOffset, e := t.oneF(buff.GetPureBuf(), wf); e != nil {
+                                       return perrors.New(e.Error(), ActionOneFFlv)
                                } else {
-                                       bufSize *= 2
+                                       if dropOffset != 0 {
+                                               _ = buff.RemoveFront(dropOffset)
+                                       } else {
+                                               break
+                                       }
                                }
                        }
                }
@@ -362,7 +358,6 @@ func (t *FlvDecoder) CutSeed(reader io.Reader, startT, duration time.Duration, w
 }
 
 func (t *FlvDecoder) GenFastSeed(reader io.Reader, save func(seedTo time.Duration, cuIndex int64) error) (err error) {
-       bufSize := humanize.KByte * 1100
        totalRead := 0
        buf := make([]byte, humanize.KByte*500)
        buff := slice.New[byte]()
@@ -370,15 +365,12 @@ func (t *FlvDecoder) GenFastSeed(reader io.Reader, save func(seedTo time.Duratio
        firstFT := -1
 
        for c := 0; err == nil && !over; c++ {
-               if buff.Size() < bufSize {
-                       n, e := reader.Read(buf)
-                       if n == 0 && errors.Is(e, io.EOF) {
-                               return io.EOF
-                       }
-                       totalRead += n
-                       err = buff.Append(buf[:n])
-                       continue
+               n, e := reader.Read(buf)
+               if n == 0 && errors.Is(e, io.EOF) {
+                       return io.EOF
                }
+               totalRead += n
+               err = buff.Append(buf[:n])
 
                if !t.init {
                        if frontBuf, dropOffset, e := t.InitFlv(buff.GetPureBuf()); e != nil {
@@ -386,29 +378,29 @@ func (t *FlvDecoder) GenFastSeed(reader io.Reader, save func(seedTo time.Duratio
                        } else {
                                if dropOffset != 0 {
                                        _ = buff.RemoveFront(dropOffset)
-                               } else {
-                                       bufSize *= 2
                                }
                                if len(frontBuf) == 0 {
                                        continue
                                }
                        }
                } else {
-                       if dropOffset, e := t.oneF(buff.GetPureBuf(), func(t, index int, buf []byte) error {
-                               if firstFT == -1 {
-                                       firstFT = t
-                               }
-                               if e := save(time.Millisecond*time.Duration(t-firstFT), int64(totalRead-buff.Size()+index)); e != nil {
-                                       return perrors.Join(ActionGenFastSeedFlv, e)
-                               }
-                               return nil
-                       }); e != nil {
-                               return perrors.Join(ActionGenFastSeedFlv, ActionOneFFlv, e)
-                       } else {
-                               if dropOffset != 0 {
-                                       _ = buff.RemoveFront(dropOffset)
+                       for {
+                               if dropOffset, e := t.oneF(buff.GetPureBuf(), func(t, index int, buf []byte) error {
+                                       if firstFT == -1 {
+                                               firstFT = t
+                                       }
+                                       if e := save(time.Millisecond*time.Duration(t-firstFT), int64(totalRead-buff.Size()+index)); e != nil {
+                                               return perrors.Join(ActionGenFastSeedFlv, e)
+                                       }
+                                       return nil
+                               }); e != nil {
+                                       return perrors.Join(ActionGenFastSeedFlv, ActionOneFFlv, e)
                                } else {
-                                       bufSize *= 2
+                                       if dropOffset != 0 {
+                                               _ = buff.RemoveFront(dropOffset)
+                                       } else {
+                                               break
+                                       }
                                }
                        }
                }
index f45c01768a5ce06e3cb18a5d4bc1cdcb0c1da597..69f5507ce1e339eed2be48b01ff486b72d564970 100644 (file)
@@ -414,7 +414,7 @@ func (t *Fmp4Decoder) Search_stream_fmp4(buf []byte, keyframe *slice.Buf[byte])
        return
 }
 
-type dealFMp4 func(t float64, index int, buf *slice.Buf[byte]) error
+type dealFMp4 func(t float64, index int, re io.Reader) error
 
 func (t *Fmp4Decoder) oneF(buf []byte, w ...dealFMp4) (cu int, err error) {
        if len(buf) > humanize.MByte*100 {
@@ -424,10 +424,7 @@ func (t *Fmp4Decoder) oneF(buf []byte, w ...dealFMp4) (cu int, err error) {
                return 0, ErrMisTraks
        }
 
-       t.buflock.Lock()
-       defer t.buflock.Unlock()
-
-       t.buf.Reset()
+       sbuf := slice.NewSliceIndexNoLock(buf)
 
        defer func() {
                if err != nil {
@@ -437,7 +434,7 @@ func (t *Fmp4Decoder) oneF(buf []byte, w ...dealFMp4) (cu int, err error) {
 
        var (
                haveKeyframe bool
-               bufModified  = t.buf.GetModified()
+               bufModified  = sbuf.GetModified()
                // maxSequenceNumber int //有时并不是单调增加
                maxVT float64
                maxAT float64
@@ -519,7 +516,7 @@ func (t *Fmp4Decoder) oneF(buf []byte, w ...dealFMp4) (cu int, err error) {
                }
 
                dropKeyFrame = func(index int) {
-                       t.buf.Reset()
+                       sbuf.Reset()
                        haveKeyframe = false
                        cu = index
                }
@@ -562,13 +559,11 @@ func (t *Fmp4Decoder) oneF(buf []byte, w ...dealFMp4) (cu int, err error) {
                                                }
                                        }
 
-                                       // fmt.Println(ts.getT(), "frame0", keyframeMoof, t.buf.size(), m[0].i, m[6].n, m[6].e)
-
                                        //deal frame
                                        if keyframeMoof {
-                                               if v, e := t.buf.HadModified(bufModified); e == nil && v && !t.buf.IsEmpty() {
+                                               if v, e := sbuf.HadModified(bufModified); e == nil && v && !sbuf.IsEmpty() {
                                                        if haveKeyframe && len(w) > 0 {
-                                                               err = w[0](video.getT(), cu, t.buf)
+                                                               err = w[0](video.getT(), cu, sbuf)
                                                                dropKeyFrame(m[0].i)
                                                                return ErrNormal
                                                        }
@@ -579,9 +574,7 @@ func (t *Fmp4Decoder) oneF(buf []byte, w ...dealFMp4) (cu int, err error) {
                                                cu = m[6].e
                                        }
                                        if haveKeyframe {
-                                               if e := t.buf.Append(buf[m[0].i:m[6].e]); e != nil {
-                                                       return e
-                                               }
+                                               sbuf.Append(m[0].i, m[6].e)
                                        }
                                        return nil
                                },
@@ -648,9 +641,9 @@ func (t *Fmp4Decoder) oneF(buf []byte, w ...dealFMp4) (cu int, err error) {
 
                                        //deal frame
                                        if keyframeMoof {
-                                               if v, e := t.buf.HadModified(bufModified); e == nil && v && !t.buf.IsEmpty() {
+                                               if v, e := sbuf.HadModified(bufModified); e == nil && v && !sbuf.IsEmpty() {
                                                        if haveKeyframe && len(w) > 0 {
-                                                               err = w[0](video.getT(), cu, t.buf)
+                                                               err = w[0](video.getT(), cu, sbuf)
                                                                dropKeyFrame(m[0].i)
                                                                return ErrNormal
                                                        }
@@ -661,251 +654,7 @@ func (t *Fmp4Decoder) oneF(buf []byte, w ...dealFMp4) (cu int, err error) {
                                                cu = m[10].e
                                        }
                                        if haveKeyframe {
-                                               if e := t.buf.Append(buf[m[0].i:m[10].e]); e != nil {
-                                                       return e
-                                               }
-                                       }
-                                       return nil
-                               },
-                       },
-               },
-       )
-
-       if errors.Is(err, ErrNormal) {
-               err = nil
-       }
-
-       return
-}
-
-func (t *Fmp4Decoder) oneFNoBuf(buf []byte, w ...dealFMp4) (cu int, err error) {
-       if len(buf) > humanize.MByte*100 {
-               return 0, ErrBufTooLarge
-       }
-       if len(t.traks) == 0 {
-               return 0, ErrMisTraks
-       }
-
-       defer func() {
-               if err != nil {
-                       cu = 0
-               }
-       }()
-
-       var (
-               haveKeyframe bool
-               // bufModified  bool
-               // maxSequenceNumber int //有时并不是单调增加
-               maxVT float64
-               maxAT float64
-
-               //get timeStamp
-               get_timeStamp = func(tfdt int) (ts timeStamp) {
-                       switch buf[tfdt+8] {
-                       case 0:
-                               ts.data = buf[tfdt+16 : tfdt+20]
-                               ts.timeStamp = int(F.Btoi32(buf, tfdt+16))
-                       case 1:
-                               ts.data = buf[tfdt+12 : tfdt+20]
-                               ts.timeStamp = int(F.Btoi64(buf, tfdt+12))
-                       }
-                       return
-               }
-
-               //get track type
-               get_track_type = func(tfhd, tfdt int) (ts timeStamp, handlerType byte) {
-                       track, ok := t.traks[int(F.Btoi(buf, tfhd+12, 4))]
-                       if ok {
-                               ts := get_timeStamp(tfdt)
-                               // if track.firstTimeStamp == -1 {
-                               //      track.firstTimeStamp = ts.timeStamp
-                               // }
-
-                               // ts.firstTimeStamp = track.firstTimeStamp
-                               ts.handlerType = track.handlerType
-                               ts.timescale = track.timescale
-
-                               // if ts.timeStamp > track.lastTimeStamp {
-                               //      track.lastTimeStamp = ts.timeStamp
-                               //      ts.resetTs()
-                               // }
-
-                               return ts, track.handlerType
-                       }
-                       return
-               }
-
-               //is SampleEntries error?
-               checkSampleEntries = func(trun, mdat int) error {
-                       if buf[trun+11] == 'b' {
-                               for i := trun + 24; i < mdat; i += 12 {
-                                       if F.Btoi(buf, i+4, 4) < 1000 {
-                                               return errors.New("find sample size less then 1000")
-                                       }
-                               }
-                       }
-                       return nil
-               }
-
-               //is t error?
-               checkAndSetMaxT = func(ts timeStamp) (err error) {
-                       switch ts.handlerType {
-                       case 'v':
-                               if maxVT == 0 {
-                                       maxVT = ts.getT()
-                               } else if maxVT == ts.getT() {
-                                       err = ActionCheckTFail.New("equal VT detect")
-                               } else if maxVT > ts.getT() {
-                                       err = ActionCheckTFail.New("lower VT detect")
-                               } else {
-                                       maxVT = ts.getT()
-                               }
-                       case 'a':
-                               if maxAT == 0 {
-                                       maxAT = ts.getT()
-                               } else if maxAT == ts.getT() {
-                                       err = ActionCheckTFail.New("equal AT detect")
-                               } else if maxAT > ts.getT() {
-                                       err = ActionCheckTFail.New("lower AT detect")
-                               } else {
-                                       maxAT = ts.getT()
-                               }
-                       default:
-                       }
-                       return
-               }
-
-               dropKeyFrame = func(index int) {
-                       haveKeyframe = false
-                       cu = index
-               }
-       )
-
-       ies, recycle, e := decode(buf, "moof")
-       defer recycle(ies)
-       if e != nil {
-               return 0, e
-       }
-
-       var ErrNormal pe.Action = "ErrNormal"
-
-       err = deals(ies,
-               []dealIE{
-                       {
-                               boxNames: []string{"moof", "mfhd", "traf", "tfhd", "tfdt", "trun", "mdat"},
-                               fs: func(m []ie) error {
-                                       var (
-                                               keyframeMoof = buf[m[5].i+20] == byte(0x02)
-                                               // moofSN       = int(F.Btoi(buf, m[1].i+12, 4))
-                                               video timeStamp
-                                       )
-
-                                       {
-                                               ts, handlerType := get_track_type(m[3].i, m[4].i)
-                                               if ts.handlerType == 'v' {
-                                                       if e := checkSampleEntries(m[5].i, m[6].i); e != nil {
-                                                               //skip
-                                                               dropKeyFrame(m[0].e)
-                                                               return pe.Join(ErrDecode, e)
-                                                       }
-                                               }
-                                               if handlerType == 'v' {
-                                                       video = ts
-                                               }
-                                               if e := checkAndSetMaxT(ts); e != nil {
-                                                       dropKeyFrame(m[0].e)
-                                                       return pe.Join(ErrDecode, e)
-                                               }
-                                       }
-
-                                       // fmt.Println(ts.getT(), "frame0", keyframeMoof, t.buf.size(), m[0].i, m[6].n, m[6].e)
-
-                                       //deal frame
-                                       if keyframeMoof {
-                                               if haveKeyframe && len(w) > 0 {
-                                                       err = w[0](video.getT(), cu, nil)
-                                                       dropKeyFrame(m[0].i)
-                                                       return ErrNormal
-                                               }
-                                               dropKeyFrame(m[0].i)
-                                               haveKeyframe = true
-                                       } else if !haveKeyframe {
-                                               cu = m[6].e
-                                       }
-                                       return nil
-                               },
-                       },
-                       {
-                               boxNames: []string{"moof", "mfhd", "traf", "tfhd", "tfdt", "trun", "traf", "tfhd", "tfdt", "trun", "mdat"},
-                               fs: func(m []ie) error {
-                                       var (
-                                               keyframeMoof = buf[m[5].i+20] == byte(0x02) || buf[m[9].i+20] == byte(0x02)
-                                               // moofSN       = int(F.Btoi(buf, m[1].i+12, 4))
-                                               video timeStamp
-                                               audio timeStamp
-                                       )
-
-                                       {
-                                               ts, handlerType := get_track_type(m[3].i, m[4].i)
-                                               if handlerType == 'v' {
-                                                       if e := checkSampleEntries(m[5].i, m[6].i); e != nil {
-                                                               //skip
-                                                               dropKeyFrame(m[0].e)
-                                                               return pe.Join(ErrDecode, e)
-                                                       }
-                                               }
-                                               switch handlerType {
-                                               case 'v':
-                                                       video = ts
-                                               case 's':
-                                                       audio = ts
-                                               }
-                                               if e := checkAndSetMaxT(ts); e != nil {
-                                                       dropKeyFrame(m[0].e)
-                                                       return pe.Join(ErrDecode, e)
-                                               }
-                                       }
-                                       {
-                                               ts, handlerType := get_track_type(m[7].i, m[8].i)
-                                               if handlerType == 'v' {
-                                                       if e := checkSampleEntries(m[9].i, m[10].i); e != nil {
-                                                               //skip
-                                                               dropKeyFrame(m[0].e)
-                                                               return pe.Join(ErrDecode, e)
-                                                       }
-                                               }
-                                               switch handlerType {
-                                               case 'v':
-                                                       video = ts
-                                               case 's':
-                                                       audio = ts
-                                               }
-                                               if e := checkAndSetMaxT(ts); e != nil {
-                                                       dropKeyFrame(m[0].e)
-                                                       return pe.Join(ErrDecode, e)
-                                               }
-                                       }
-
-                                       //sync audio timeStamp
-                                       if t.AVTDiff <= 0.1 {
-                                               t.AVTDiff = 0.1
-                                       }
-                                       if diff := math.Abs(video.getT() - audio.getT()); diff > t.AVTDiff {
-                                               return pe.Join(ErrDecode, fmt.Errorf("时间戳不匹配 %v %v (或许应调整fmp4音视频时间戳容差s>%.2f)", video.timeStamp, audio.timeStamp, diff))
-                                               // copy(video.data, F.Itob64(int64(audio.getT()*float64(video.timescale))))
-                                       }
-
-                                       //deal frame
-                                       if keyframeMoof {
-                                               if haveKeyframe && len(w) > 0 {
-                                                       err = w[0](video.getT(), cu, nil)
-                                                       dropKeyFrame(m[0].i)
-                                                       return ErrNormal
-                                               }
-                                               dropKeyFrame(m[0].i)
-                                               haveKeyframe = true
-                                       } else if !haveKeyframe {
-                                               cu = m[10].e
+                                               sbuf.Append(m[0].i, m[10].e)
                                        }
                                        return nil
                                },
@@ -927,7 +676,6 @@ func (t *Fmp4Decoder) Cut(reader io.Reader, startT, duration time.Duration, w io
 
 func (t *Fmp4Decoder) CutSeed(reader io.Reader, startT, duration time.Duration, w io.Writer, seeker io.Seeker, getIndex func(seedTo time.Duration) (int64, error)) (err error) {
        buf := make([]byte, humanize.MByte*3)
-       buff := slice.New[byte]()
        init := false
        seek := false
        over := false
@@ -935,14 +683,18 @@ func (t *Fmp4Decoder) CutSeed(reader io.Reader, startT, duration time.Duration,
        durationM := duration.Seconds()
        firstFT := -1.0
 
-       wf := func(t float64, index int, buf *slice.Buf[byte]) (e error) {
+       t.buflock.Lock()
+       defer t.buflock.Unlock()
+       t.buf.Reset()
+
+       wf := func(t float64, index int, re io.Reader) (e error) {
                if firstFT == -1 {
                        firstFT = t
                }
                cu := t - firstFT
                over = duration != 0 && cu > durationM+startTM
                if startTM <= cu && !over {
-                       _, e = w.Write(buf.GetPureBuf())
+                       _, e = io.Copy(w, re)
                }
                return
        }
@@ -955,10 +707,10 @@ func (t *Fmp4Decoder) CutSeed(reader io.Reader, startT, duration time.Duration,
                if n == 0 && errors.Is(e, io.EOF) {
                        return io.EOF
                }
-               err = buff.Append(buf[:n])
+               err = t.buf.Append(buf[:n])
 
                if !init {
-                       if frontBuf, e := t.Init_fmp4(buff.GetPureBuf()); e != nil {
+                       if frontBuf, e := t.Init_fmp4(t.buf.GetPureBuf()); e != nil {
                                return pe.New(e.Error(), ActionInitFmp4)
                        } else {
                                if len(frontBuf) == 0 {
@@ -982,14 +734,14 @@ func (t *Fmp4Decoder) CutSeed(reader io.Reader, startT, duration time.Duration,
                                }
                                seek = true
                                startTM = 0
-                               buff.Clear()
+                               t.buf.Reset()
                        }
                        for {
-                               if dropOffset, e := t.oneF(buff.GetPureBuf(), wf); e != nil {
+                               if dropOffset, e := t.oneF(t.buf.GetPureBuf(), wf); e != nil {
                                        return pe.New(e.Error(), ActionOneFFmp4)
                                } else {
                                        if dropOffset != 0 {
-                                               _ = buff.RemoveFront(dropOffset)
+                                               _ = t.buf.RemoveFront(dropOffset)
                                        } else {
                                                break
                                        }
@@ -1028,7 +780,7 @@ func (t *Fmp4Decoder) GenFastSeed(reader io.Reader, save func(seedTo time.Durati
                        }
                } else {
                        for {
-                               if dropOffset, e := t.oneFNoBuf(t.buf.GetPureBuf(), func(ts float64, index int, _ *slice.Buf[byte]) error {
+                               if dropOffset, e := t.oneF(t.buf.GetPureBuf(), func(ts float64, index int, _ io.Reader) error {
                                        if firstFT == -1 {
                                                firstFT = ts
                                        }
diff --git a/go.mod b/go.mod
index 29a2dfa794669b5f5f8a896d01960878f5ccf664..d9ab673042bf8beb2956e1190fc689b2f223802a 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.20250514163050
+       github.com/qydysky/part v0.28.20250518084643
        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.24.0 // indirect
diff --git a/go.sum b/go.sum
index 70d831f925676abee7bae8cd24a386dbdeecf005..e036c9d7aac6582431510426be185e27dd2da725 100644 (file)
--- a/go.sum
+++ b/go.sum
@@ -46,8 +46,8 @@ github.com/qydysky/biliApi v0.0.0-20250509210314-8407d638b123 h1:YXqqrwvzFwfs8Lo
 github.com/qydysky/biliApi v0.0.0-20250509210314-8407d638b123/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.20250514163050 h1:BQ7pNQWmXkW/sP2wQchORO3L0cGW6cX8jqvlQVGm5G4=
-github.com/qydysky/part v0.28.20250514163050/go.mod h1:wp71PQdKYcg9jn9yDDvqC4shS/kzejyvFqbfUxuHocY=
+github.com/qydysky/part v0.28.20250518084643 h1:6hhY+O/pe4ynY7tL21VDZ9kVMr+i0bBAD4F5TfNHRA8=
+github.com/qydysky/part v0.28.20250518084643/go.mod h1:wp71PQdKYcg9jn9yDDvqC4shS/kzejyvFqbfUxuHocY=
 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=