### 说明
本项目使用github action自动构建,构建过程详见[yml](https://github.com/qydysky/bili_danmu/blob/master/.github/workflows/go.yml)
+#### 指定房间录制区间
+配置文件中添加配置项`指定房间录制区间`。
+
+指定roomid的房间在指定时间段内将会开启录制。
+
+- `start`时检查是否在直播,是则开始录制,如已在录制则切片。
+- `end`时如已在录制则停止录制。
+- 在开播时,若在`start`与`end`之间,则录制,不在则不录制(仅检查同时有`start`,`end`的`fromTo`)。
+- 5s内只能触发一个`fromTo`,所以同房间各`start`、`end`之间间隔不要少于5s。
+
+```json
+{
+ "指定房间录制区间":[
+ {
+ "roomid":0,
+ "fromTo":[
+ {
+ "start": "12:01:00",
+ "end": "12:03:00"
+ },
+ {
+ "start": "12:02:00"
+ }
+ ]
+ }
+ ]
+}
+```
+
#### 保存日志至DB
配置文件中添加配置项`保存日志至db`。大部分可以参考保存弹幕至db,但有些许不同:
c "github.com/qydysky/bili_danmu/CV"
F "github.com/qydysky/bili_danmu/F"
_ "github.com/qydysky/bili_danmu/Reply/F"
+ "github.com/qydysky/bili_danmu/Reply/F/danmuXml"
send "github.com/qydysky/bili_danmu/Send"
p "github.com/qydysky/part"
- comp "github.com/qydysky/part/component"
pctx "github.com/qydysky/part/ctx"
file "github.com/qydysky/part/file"
pio "github.com/qydysky/part/io"
return
} else if !file.New(v+"0.xml", 0, true).IsExist() {
type empty struct{}
- if e := comp.Run(comp.Sign[empty](`SerF`, `player`, `ws`), context.Background(), &v); e != nil {
+ if e := danmuXml.DanmuXml.Run(context.Background(), &v); e != nil {
msglog.L(`E: `, e)
}
}
}
type empty struct{}
- if e := comp.Run(comp.Sign[empty](`SerF`, `player`, `xml`), context.Background(), &v); e != nil {
+ if e := danmuXml.DanmuXml.Run(context.Background(), &v); e != nil {
msglog.L(`E: `, e)
}
}
// 弹幕录制结束
type empty struct{}
- if e := comp.Run[string](comp.Sign[empty](`startRecDanmu`, `stop`), context.Background(), &filePath); e != nil {
- f.L(`E: `, e)
+ if e := danmuXml.DanmuXml.Run(context.Background(), &filePath); e != nil {
+ msglog.L(`E: `, e)
}
Recoder.Stop()
package f
-
-import (
- "github.com/qydysky/bili_danmu/Reply/F/danmuXml"
- "github.com/qydysky/bili_danmu/Reply/F/liveOver"
- "github.com/qydysky/bili_danmu/Reply/F/reSetMp4TimeStamp"
- comp "github.com/qydysky/part/component"
-)
-
-func init() {
- var linkMap = map[string][]string{
- "github.com/qydysky/bili_danmu/Reply.startRecDanmu.stop": {
- comp.Sign[danmuXml.Sign](`toXml`),
- comp.Sign[reSetMp4TimeStamp.Sign](`_resetTS`),
- // comp.Sign[fmp4Tomp4.Sign](`conver`),
- },
- "github.com/qydysky/bili_danmu/Reply.SerF.player.ws": {
- comp.Sign[danmuXml.Sign](`toXml`),
- },
- "github.com/qydysky/bili_danmu/Reply.SerF.player.xml": {
- comp.Sign[danmuXml.Sign](`toXml`),
- },
- "github.com/qydysky/bili_danmu/Reply.preparing": {
- comp.Sign[liveOver.Sign](`sumup`),
- },
- }
- if e := comp.Link(linkMap); e != nil {
- panic(e)
- }
-}
file "github.com/qydysky/part/file"
)
-type Sign struct {
- // path: csv所在目录,末尾无
- toXml func(ctx context.Context, path *string) error
-}
-
-func init() {
- sign := Sign{
- toXml: toXml,
- }
- if e := comp.Put[string](comp.Sign[Sign](`toXml`), sign.toXml); e != nil {
- panic(e)
- }
-}
+// path
+var DanmuXml = comp.NewComp(toXml)
type danmu struct {
XMLName xml.Name `xml:"i"`
+++ /dev/null
-package fmp4Tomp4
-
-import (
- "bytes"
- "context"
- "errors"
- "fmt"
- "time"
-
- comp "github.com/qydysky/part/component"
- file "github.com/qydysky/part/file"
-)
-
-// 直接保存下来的mp4在chrome上无法直接播放
-//
-// https://serverfault.com/questions/738881/chrome-makes-way-too-many-requests-22000-while-downloading-mp4-video-34mb
-type Sign struct {
- // 重设mp4的时间戳
- conver func(ctx context.Context, ptr *string) error
-}
-
-func init() {
- sign := Sign{
- conver: conver,
- }
- if e := comp.Put[string](comp.Sign[Sign](`conver`), sign.conver); e != nil {
- panic(e)
- }
-}
-
-var (
- ErrParse = errors.New("ErrParse")
-)
-
-func conver(ctx context.Context, ptr *string) error {
- be := time.Now()
- fmt.Println("conver")
- defer func() { fmt.Printf("conver fin (%v)\n", time.Since(be)) }()
-
- sf := file.New(*ptr+"0.mp4", 0, false)
- if !sf.IsExist() {
- return nil
- }
- defer sf.Close()
-
- r, _ := NewReader(sf)
- if e := r.Parse(); e != nil || len(r.Boxs) == 0 {
- return e
- }
- traks, e := r.ParseTrun()
- if e != nil {
- return e
- }
- fmt.Printf("conver parse ok (%v)\n", time.Since(be))
-
- boxReader := r.Read(r.getN("mvhd", 1)[0], 30)
- timescale := boxReader.I32(20)
- scaleDur := boxReader.I32(24)
- mainDuration := float64(scaleDur) / float64(timescale)
-
- tf := file.New(*ptr+"1.mp4", 0, false)
- if tf.IsExist() {
- _ = tf.Delete()
- }
- defer tf.Close()
-
- w, e := NewBoxWriter(tf)
- if e != nil {
- return e
- }
-
- // ftyp
- {
- ftyp := w.Box("ftyp")
- ftyp.Write([]byte("isom"))
- ftyp.Write(itob32(512))
- ftyp.Write([]byte("isom"))
- ftyp.Write([]byte("iso2"))
- ftyp.Write([]byte("avc1"))
- ftyp.Write([]byte("mp41"))
- if e := ftyp.Close(); e != nil {
- return e
- }
- }
-
- // moov
- {
- moov := w.Box("moov")
- // mvhd
- {
- mvhd := moov.Box("mvhd")
- mvhd.Write(make([]byte, 12))
- mvhd.Write(itob32(1000))
- mvhd.Write(itob32(int32(mainDuration * 1000)))
- mvhd.Write(r.Read(r.getN("mvhd", 1)[0], -1).Buf[28:])
- if e := mvhd.Close(); e != nil {
- return e
- }
- }
- // trak
- var trakCount = -1
- for trakId, trakSum := range traks {
- fmt.Printf("conver traks (%v)(%v)\n", trakId, time.Since(be))
-
- trakCount++
- trak := moov.Box("trak")
- // tkhd
- {
- if boxs := r.getN("trak", 2); len(boxs) != 2 {
- return errors.Join(ErrParse, fmt.Errorf("trak"))
- } else {
- tkhd := trak.Box("tkhd")
- tkhd.Write([]byte{0, 0, 0, 3})
- tkhd.Write(make([]byte, 8))
- tkhd.Write(itob32(trakId))
- tkhd.Write(make([]byte, 4))
- tkhd.Write(itob32(int32(mainDuration * 1000)))
- tkhd.Write(r.Read(boxs[trakCount], -1).Buf[32:])
- if e := tkhd.Close(); e != nil {
- return e
- }
- }
- }
- // mdia
- {
- mdia := trak.Box("mdia")
- // mdhd
- {
- if boxs := r.getN("mdhd", 2); len(boxs) != 2 {
- return errors.Join(ErrParse, fmt.Errorf("mdhd"))
- } else {
- mdhd := mdia.Box("mdhd")
- mdhd.Write(r.Read(boxs[trakCount], -1).Buf)
- if e := mdhd.Close(); e != nil {
- return e
- }
- }
- }
- // hdlr
- var handlerType = make([]byte, 4)
- {
- if boxs := r.getN("hdlr", 2); len(boxs) != 2 {
- return errors.Join(ErrParse, fmt.Errorf("hdlr"))
- } else {
- hdlr := mdia.Box("hdlr")
- boxReader := r.Read(boxs[trakCount], -1)
- copy(handlerType, boxReader.Buf[16:20])
- hdlr.Write(boxReader.Buf)
- if e := hdlr.Close(); e != nil {
- return e
- }
- }
- }
- // minf
- {
- minf := mdia.Box("minf")
- // vmhd
- if bytes.Equal(handlerType, []byte("vide")) {
- if boxs := r.getN("vmhd", 1); len(boxs) != 1 {
- return errors.Join(ErrParse, fmt.Errorf("vmhd"))
- } else {
- vmhd := minf.Box("vmhd")
- vmhd.Write(r.Read(boxs[0], -1).Buf)
- if e := vmhd.Close(); e != nil {
- return e
- }
- }
- }
- // dinf
- {
- if boxs := r.getN("dinf", 2); len(boxs) != 2 {
- return errors.Join(ErrParse, fmt.Errorf("dinf"))
- } else {
- dinf := minf.Box("dinf")
- dinf.Write(r.Read(boxs[trakCount], -1).Buf)
- if e := dinf.Close(); e != nil {
- return e
- }
- }
- }
- // stbl
- {
- stbl := minf.Box("stbl")
- // stsd
- {
- if boxs := r.getN("stsd", 2); len(boxs) != 2 {
- return errors.Join(ErrParse, fmt.Errorf("stsd"))
- } else {
- stsd := stbl.Box("stsd")
- stsd.Write(r.Read(boxs[trakCount], -1).Buf)
- if e := stsd.Close(); e != nil {
- return e
- }
- }
- }
- // stts
- {
- stts := stbl.Box("stts")
- stts.Write([]byte{0, 0, 0, 0})
- stts.Write(itob32(int32(len(trakSum.dur))))
- for k, v := range trakSum.dur {
- stts.Write(itob32(int32(k)))
- stts.Write(itob32(v))
- }
- if e := stts.Close(); e != nil {
- return e
- }
- }
- // stsc
- {
- stsc := stbl.Box("stsc")
- stsc.Write([]byte{0, 0, 0, 0})
- stsc.Write(itob32(int32(len(trakSum.sampleCount))))
- for k, v := range trakSum.sampleCount {
- stsc.Write(itob32(int32(k)))
- stsc.Write(itob32(v))
- stsc.Write([]byte{0, 0, 0, 1})
- }
- if e := stsc.Close(); e != nil {
- return e
- }
- }
- // stsz
- {
- stsz := stbl.Box("stsz")
- stsz.Write([]byte{0, 0, 0, 0})
- stsz.Write(itob32(int32(len(trakSum.size))))
- for _, v := range trakSum.size {
- stsz.Write(itob32(v))
- }
- if e := stsz.Close(); e != nil {
- return e
- }
- }
- // co64
- {
- co64 := stbl.Box("co64")
- co64.Write([]byte{0, 0, 0, 0})
- co64.Write(itob32(int32(len(trakSum.chunkSize))))
-
- cuIndex, _ := tf.CurIndex()
- cuIndex += int64(8*len(trakSum.chunkSize)) + 8
-
- co64.Write(itob64(cuIndex))
- for i := 0; i < len(trakSum.chunkSize)-1; i++ {
- co64.Write(itob64(cuIndex + trakSum.chunkSize[i]))
- }
- if e := co64.Close(); e != nil {
- return e
- }
- }
- if e := stbl.Close(); e != nil {
- return e
- }
- }
- if e := minf.Close(); e != nil {
- return e
- }
- }
- if e := mdia.Close(); e != nil {
- return e
- }
- }
- if e := trak.Close(); e != nil {
- return e
- }
- }
- if e := moov.Close(); e != nil {
- return e
- }
- fmt.Printf("conver moov fin (%v)\n", time.Since(be))
- }
-
- // mdat
- {
- mdat := w.Box("mdat")
- for i := 0; i < len(r.Boxs); i++ {
- box := r.Boxs[i]
- if box.Name == "mdat" {
- if e := sf.SeekIndex(box.Index+box.HeaderSize, file.AtOrigin); e != nil {
- return e
- }
- mdat.CopyFrom(sf, uint64(box.Size-box.HeaderSize))
- }
- }
- if e := mdat.Close(); e != nil {
- return e
- }
- }
- return nil
-}
-
-// func bufChange(buf []byte, size int) {
-// if n := size - len(buf); n > 0 {
-// if size <= cap(buf) {
-// buf = buf[:size]
-// } else {
-// buf = append(buf, make([]byte, n)...)
-// }
-// } else if n < 0 {
-// buf = buf[:size]
-// }
-// clear(buf)
-// }
-
-// type track struct {
-// chunkSize []int64
-// sampleCount []int32
-// dur []int32
-// size []int32
-// }
-
-// type wt struct {
-// sf *reader
-// f *file.File
-// buf []byte
-// m map[string]float64
-// }
-
-// func NewWt(sf *reader, tf *file.File) *wt {
-// return &wt{
-// sf: sf,
-// f: tf,
-// buf: make([]byte, 1<<20),
-// m: make(map[string]float64),
-// }
-// }
-
-// func (t *wt) start(boxName string, wrongPanic bool) (wSize int) {
-// if _, e := t.f.Write([]byte{0, 0, 0, 1}, false); e != nil {
-// panic(e)
-// }
-// if _, e := t.f.Write([]byte(boxName), false); e != nil {
-// panic(e)
-// }
-// if _, e := t.f.Write(make([]byte, 8), false); e != nil {
-// panic(e)
-// }
-// wSize = 16
-// return
-// }
-
-// func (t *wt) skipt(size int) (n int) {
-// if size == 0 {
-// return
-// }
-// t.bufChange(size)
-// if n, e := io.Writer(t.f.File()).Write(t.buf); e != nil {
-// panic(e)
-// } else {
-// return n
-// }
-// }
-
-// func (t *wt) skips(size int64) (n int64) {
-// if size == 0 {
-// return
-// }
-// if e := t.sf.f.SeekIndex(size, file.AtCurrent); e != nil {
-// panic(e)
-// }
-// return size
-// }
-
-// func (t *wt) w(p []byte) (n int) {
-// if n, e := t.f.Write(p, false); e != nil {
-// panic(e)
-// } else {
-// return n
-// }
-// }
-
-// func (t *wt) r(size int) (n int64) {
-// t.bufChange(size)
-// if n, e := t.sf.f.Read(t.buf); e != nil {
-// panic(e)
-// } else {
-// return int64(n)
-// }
-// }
-
-// func (t *wt) bufChange(size int) {
-// if n := size - len(t.buf); n > 0 {
-// if size <= cap(t.buf) {
-// t.buf = t.buf[:size]
-// } else {
-// t.buf = append(t.buf, make([]byte, n)...)
-// }
-// } else if n < 0 {
-// t.buf = t.buf[:size]
-// }
-// clear(t.buf)
-// }
-
-// func (t *wt) fin(size int) int {
-// if e := t.f.SeekIndex(-int64(size), file.AtCurrent); e != nil {
-// panic(e)
-// }
-// if e := t.f.SeekIndex(8, file.AtCurrent); e != nil {
-// panic(e)
-// }
-// if _, e := t.f.Write(itob64(int64(size)), false); e != nil {
-// panic(e)
-// }
-// if e := t.f.SeekIndex(int64(size-16), file.AtCurrent); e != nil {
-// panic(e)
-// }
-// return size
-// }
-
-// func (t *wt) copyBox(boxName string) int {
-// wSize := t.start(boxName, true)
-// t.r(int(lSize))
-// wSize += t.w(t.buf)
-// return t.fin(wSize)
-// }
-
-// func (t *wt) ftyp() {
-// n := t.start("ftyp", true)
-// n += t.w([]byte("isom"))
-// n += t.w(itob32(512))
-// n += t.w([]byte("isom"))
-// n += t.w([]byte("iso2"))
-// n += t.w([]byte("avc1"))
-// n += t.w([]byte("mp41"))
-// t.fin(n)
-// }
-
-// func (t *wt) moov() {
-// n, _ := t.start("moov", true)
-// n += t.mvhd()
-// n += t.trak()
-// t.fin(n)
-// }
-
-// func (t *wt) mvhd() int {
-// wSize, lSize := t.start("mvhd", true)
-// lSize -= t.skips(12)
-// lSize -= t.r(4)
-// timescale := btoi32(t.buf, 0)
-// lSize -= t.r(4)
-// duration := btoi32(t.buf, 0)
-// t.m["mainDuration"] = float64(duration) / float64(timescale)
-// wSize += t.skipt(12)
-// wSize += t.w(itob32(1000))
-// wSize += t.w(itob32(timescale * duration / 1000))
-// lSize -= t.r(56)
-// wSize += t.w(t.buf)
-// t.skips(lSize)
-// return t.fin(wSize)
-// }
-
-// func (t *wt) trak() int {
-// wSize, _ := t.start("trak", false)
-// if wSize == 0 {
-// return 0
-// }
-
-// wSize += t.tkhd()
-// wSize += t.mdia()
-
-// return t.fin(wSize)
-// }
-
-// func (t *wt) tkhd() int {
-// wSize, lSize := t.start("mvhd", true)
-// lSize -= t.r(12)
-// wSize += t.w(t.buf)
-// lSize -= t.r(4)
-// wSize += t.w(t.buf)
-// wSize += t.skipt(4)
-// wSize += t.w(itob32(int32(t.m["mainDuration"] / 1000)))
-// wSize += t.skipt(10)
-// lSize -= t.skips(18)
-// lSize -= t.r(52)
-// wSize += t.w(t.buf)
-// t.skips(lSize)
-// return t.fin(wSize)
-// }
-
-// func (t *wt) mdia() int {
-// wSize, _ := t.start("mdia", false)
-// if wSize == 0 {
-// return 0
-// }
-
-// wSize += t.mdhd()
-// wSize += t.copyBox(`hdlr`)
-// wSize += t.minf()
-
-// return t.fin(wSize)
-// }
-
-// func (t *wt) mdhd() int {
-// wSize, lSize := t.start("mdhd", true)
-// lSize -= t.r(16)
-// wSize += t.w(t.buf)
-// lSize -= t.skips(8)
-// wSize += t.w(itob32(1000))
-// wSize += t.w(itob32(int32(t.m["mainDuration"] / 1000)))
-// lSize -= t.r(4)
-// wSize += t.w(t.buf)
-// t.skips(lSize)
-// return t.fin(wSize)
-// }
-
-// func (t *wt) minf() int {
-// wSize, _ := t.start("mdia", false)
-// if wSize == 0 {
-// return 0
-// }
-
-// wSize += t.copyBox(`vmhd`)
-// wSize += t.copyBox(`dinf`)
-// wSize += t.stbl()
-
-// return t.fin(wSize)
-// }
-
-// func (t *wt) stbl() int {
-// wSize, _ := t.start("stbl", false)
-// if wSize == 0 {
-// return 0
-// }
-
-// wSize += t.copyBox(`stsd`)
-// wSize += t.stts()
-
-// return t.fin(wSize)
-// }
-
-// func (t *wt) stts() int {
-// wSize, lSize := t.start("stts", true)
-// for {
-// lSize -= t.r(1 << 20)
-// }
-// // lSize -= t.r(16)
-// // wSize += t.w(t.buf)
-// // lSize -= t.skips(8)
-// // wSize += t.w(itob32(1000))
-// // wSize += t.w(itob32(int32(t.m["mainDuration"] / 1000)))
-// // lSize -= t.r(4)
-// // wSize += t.w(t.buf)
-// t.skips(lSize)
-// return t.fin(wSize)
-// }
-
-func btoi64(b []byte, offset int) int64 {
- s := 8
- bu := make([]byte, s)
- l := len(b) - offset
- if l > s {
- l = s
- }
- for i := 0; i < s && i < l; i++ {
- bu[i+s-l] = b[offset+i]
- }
-
- //binary.BigEndian.Uint64
- return int64(uint64(bu[7]) | uint64(bu[6])<<8 | uint64(bu[5])<<16 | uint64(bu[4])<<24 |
- uint64(bu[3])<<32 | uint64(bu[2])<<40 | uint64(bu[1])<<48 | uint64(bu[0])<<56)
-}
-
-func btoi32(b []byte, offset int) int32 {
- s := 4
- bu := make([]byte, s)
- l := len(b) - offset
- if l > s {
- l = s
- }
- for i := 0; i < s && i < l; i++ {
- bu[i+s-l] = b[offset+i]
- }
-
- //binary.BigEndian.Uint32
- return int32((uint32(bu[3]) | uint32(bu[2])<<8 | uint32(bu[1])<<16 | uint32(bu[0])<<24))
-}
-
-func itob64(v int64) []byte {
- //binary.BigEndian.PutUint64
- b := make([]byte, 8)
- b[0] = byte(v >> 56)
- b[1] = byte(v >> 48)
- b[2] = byte(v >> 40)
- b[3] = byte(v >> 32)
- b[4] = byte(v >> 24)
- b[5] = byte(v >> 16)
- b[6] = byte(v >> 8)
- b[7] = byte(v)
- return b
-}
-
-func itob32(v int32) []byte {
- //binary.BigEndian.PutUint32
- b := make([]byte, 4)
- b[0] = byte(v >> 24)
- b[1] = byte(v >> 16)
- b[2] = byte(v >> 8)
- b[3] = byte(v)
- return b
-}
+++ /dev/null
-package fmp4Tomp4
-
-// func Test_conver(t *testing.T) {
-// path := "/codefile/testdata/"
-// if e := conver(context.Background(), &path); e != nil {
-// t.Fatal(e)
-// }
-// }
+++ /dev/null
-package fmp4Tomp4
-
-import (
- "bytes"
- "errors"
- "fmt"
-
- file "github.com/qydysky/part/file"
- "golang.org/x/exp/slices"
-)
-
-type Box struct {
- Name string
- Size int64
- HeaderSize int64
- Index int64
-}
-
-type reader struct {
- f *file.File
- Boxs []Box
- buf []byte
-}
-
-func NewReader(f *file.File) (*reader, error) {
- if f.Config.AutoClose {
- return nil, errors.New("file AutoClose must false")
- }
- return &reader{f: f}, nil
-}
-
-func (t *reader) getN(name string, n int) (ls []Box) {
- for i := 0; i < len(t.Boxs); i++ {
- box := t.Boxs[i]
- if box.Name == name {
- ls = append(ls, box)
- if len(ls) >= n {
- return
- }
- }
- }
- return
-}
-
-type BoxReader struct {
- Buf []byte
-}
-
-func (t *BoxReader) I32(offset int) int32 {
- return btoi32(t.Buf, offset)
-}
-func (t *BoxReader) I64(offset int) int64 {
- return btoi64(t.Buf, offset)
-}
-
-func (t *reader) Read(box Box, size int) BoxReader {
- if size == -1 {
- t.bufChange(int(box.Size))
- } else {
- t.bufChange(size)
- }
- _ = t.f.SeekIndex(box.Index, file.AtOrigin)
- _, _ = t.f.Read(t.buf)
- return BoxReader{t.buf}
-}
-
-func (t *reader) bufChange(size int) {
- if n := size - len(t.buf); n > 0 {
- if size <= cap(t.buf) {
- t.buf = t.buf[:size]
- } else {
- t.buf = append(t.buf, make([]byte, n)...)
- }
- } else if n < 0 {
- t.buf = t.buf[:size]
- }
- clear(t.buf)
-}
-
-// 正常将返回io.EOF
-func (t *reader) Parse() error {
- stat, e := t.f.Stat()
- if e != nil {
- return e
- }
-
- var (
- b4 = make([]byte, 4)
- b8 = make([]byte, 8)
- parseInside = []string{"moov", "trak", "mdia", "minf", "stbl", "moof", "traf"}
- )
- for i := int64(0); i < stat.Size(); {
- boxHeaderSize := 0
- if n, e := t.f.Read(b4); e != nil {
- return e
- } else {
- boxHeaderSize += n
- }
- size := int64(btoi32(b4, 0))
- if n, e := t.f.Read(b4); e != nil {
- return e
- } else {
- boxHeaderSize += n
- }
- name := string(b4)
- if size == 1 {
- if n, e := t.f.Read(b8); e != nil {
- return e
- } else {
- boxHeaderSize += n
- }
- size = btoi64(b8, 0)
- }
- t.Boxs = append(t.Boxs, Box{
- Name: name,
- Size: size,
- Index: i,
- HeaderSize: int64(boxHeaderSize),
- })
- if !slices.Contains(parseInside, name) {
- seedSize := size - int64(boxHeaderSize)
- if e := t.f.SeekIndex(seedSize, file.AtCurrent); e != nil {
- return e
- }
- i += size
- } else {
- i += int64(boxHeaderSize)
- }
- }
- return nil
-}
-
-type Track struct {
- chunkSize []int64
- sampleCount []int32
- dur []int32
- size []int32
-}
-
-func (t *reader) ParseTrun() (tracks map[int32]*Track, err error) {
- tracks = make(map[int32]*Track)
-
- var (
- b4 = make([]byte, 4)
- b8 = make([]byte, 8)
- b12 = make([]byte, 12)
- )
-
- for i := 0; i < len(t.Boxs); i++ {
- box := t.Boxs[i]
- if box.Name == "tfhd" {
- _ = t.f.SeekIndex(box.Index+box.HeaderSize, file.AtOrigin)
-
- if _, e := t.f.Read(b8); e != nil {
- err = e
- return
- }
- trackID := btoi32(b8, 4)
- defaultSampleDuration := int32(0)
- {
- var offset int64
- if b8[3]&0x01 == 0x01 {
- offset += 8
- }
- if b8[3]&0x02 == 0x02 {
- offset += 4
- }
- if b8[3]&0x08 == 0x08 {
- _ = t.f.SeekIndex(offset, file.AtCurrent)
- if _, e := t.f.Read(b4); e != nil {
- err = e
- return
- }
- }
- defaultSampleDuration = btoi32(b4, 0)
- }
-
- trackO, ok := tracks[trackID]
- if !ok {
- tracks[trackID] = &Track{}
- trackO = tracks[trackID]
- }
-
- for ; i < len(t.Boxs); i++ {
- box := t.Boxs[i]
- if box.Name == "trun" {
- _ = t.f.SeekIndex(box.Index+box.HeaderSize, file.AtOrigin)
- if _, e := t.f.Read(b8); e != nil {
- err = e
- return
- }
-
- _ = t.f.SeekIndex(8, file.AtCurrent)
-
- var chunkSize int64
- if bytes.Equal(b8[:4], []byte{0x01, 0x00, 0x0b, 0x05}) {
- sampleCount := btoi32(b8, 4)
- if sampleCount*12 != int32(box.Size-24) {
- err = errors.New("wrong trun trunSize not match sampleCount")
- return
- }
- trackO.sampleCount = append(trackO.sampleCount, sampleCount)
- for i := int32(0); i < sampleCount; i++ {
- if _, e := t.f.Read(b12); e != nil {
- err = e
- return
- }
- trackO.dur = append(trackO.dur, btoi32(b12, 0))
- trackO.size = append(trackO.size, btoi32(b12, 4))
- chunkSize += int64(btoi32(b12, 4))
- }
- } else if bytes.Equal(b8[:4], []byte{0x01, 0x00, 0x02, 0x05}) {
- sampleCount := btoi32(b8, 4)
- if sampleCount*4 != int32(box.Size-24) {
- err = errors.New("wrong trun trunSize not match sampleCount")
- return
- }
- trackO.sampleCount = append(trackO.sampleCount, sampleCount)
- for i := int32(0); i < sampleCount; i++ {
- if _, e := t.f.Read(b4); e != nil {
- err = e
- return
- }
- trackO.dur = append(trackO.dur, defaultSampleDuration)
- trackO.size = append(trackO.size, btoi32(b4, 0))
- chunkSize += int64(btoi32(b4, 0))
- }
- } else {
- err = fmt.Errorf("wrong trun tr_flag(%v)", b8[:4])
- return
- }
- trackO.chunkSize = append(trackO.chunkSize, chunkSize)
-
- break
- }
- }
- }
- }
-
- return
-}
+++ /dev/null
-package fmp4Tomp4
-
-// func Test_parse(t *testing.T) {
-// var read = reader{
-// f: file.New("/codefile/testdata/0.mp4", 0, false),
-// }
-// if e := read.Parse(); e != nil && !errors.Is(e, io.EOF) {
-// t.Fatal(e)
-// }
-// t.Log(len(read.Boxs))
-
-// m, e := read.ParseTrun()
-// t.Log(m)
-// t.Log(e)
-// // for i := 0; i < len(read.boxs); i++ {
-// // t.Log(read.boxs[i].name)
-// // }
-// }
+++ /dev/null
-package fmp4Tomp4
-
-import (
- "errors"
- "fmt"
-
- file "github.com/qydysky/part/file"
- pio "github.com/qydysky/part/io"
-)
-
-var (
- ErrFileAutoClose = errors.New("file AutoClose must false")
- ErrSeed = errors.New("ErrSeed")
- ErrWrite = errors.New("ErrWrite")
-)
-
-type boxWriter struct {
- f *file.File
- wn int64
- e error
- p *boxWriter
-}
-
-func NewBoxWriter(f *file.File) (t *boxWriter, err error) {
- if f.Config.AutoClose {
- return nil, ErrFileAutoClose
- }
- t = &boxWriter{f: f}
- return
-}
-
-func (t *boxWriter) Box(name string) (tc *boxWriter) {
- if t.e != nil {
- return
- }
- tc = &boxWriter{f: t.f, p: t, e: t.e}
- tc.Write([]byte{0, 0, 0, 1})
- tc.Write([]byte(name))
- tc.wn = 0
- tc.Write(make([]byte, 8))
- return
-}
-
-func (t *boxWriter) Write(b []byte) (tc *boxWriter) {
- if t.e != nil {
- return t
- }
- n := 0
- n, t.e = t.f.Write(b, false)
- t.wn += int64(n)
- return t
-}
-
-func (t *boxWriter) CopyFrom(f *file.File, size uint64) (tc *boxWriter) {
- if t.e != nil {
- return t
- }
- t.e = f.CopyTo(t.f, pio.CopyConfig{MaxByte: size}, false)
- t.wn += int64(size)
- return t
-}
-
-func (t *boxWriter) Close() error {
- if t.e != nil {
- return t.e
- }
- t.e = t.f.SeekIndex(-t.wn, file.AtCurrent)
- if t.e != nil {
- return errors.Join(ErrSeed, t.e, fmt.Errorf("Arg %v", -t.wn))
- }
- _, t.e = t.f.Write(itob64(t.wn), false)
- if t.e != nil {
- return errors.Join(ErrWrite, t.e, fmt.Errorf("Arg %v", -t.wn))
- }
- t.e = t.f.SeekIndex(t.wn-8, file.AtCurrent)
- if t.p != nil {
- t.p.wn += t.wn
- }
- return t.e
-}
+++ /dev/null
-package fmp4Tomp4
-
-// func Test_w(t *testing.T) {
-// tf := file.New("1.mp4", 0, false)
-// if tf.IsExist() {
-// _ = tf.Delete()
-// }
-// defer tf.Close()
-
-// w, e := NewBoxWriter(tf)
-// if e != nil {
-// t.Fatal(e)
-// }
-
-// // ftyp
-// {
-// ftyp := w.Box("ftyp")
-// ftyp.Write([]byte("isom"))
-// ftyp.Write(itob32(512))
-// ftyp.Write([]byte("isom"))
-// ftyp.Write([]byte("iso2"))
-// ftyp.Write([]byte("avc1"))
-// ftyp.Write([]byte("mp41"))
-// if e := ftyp.Close(); e != nil {
-// t.Fatal(e)
-// }
-// }
-// }
comp "github.com/qydysky/part/component"
)
-type Sign struct {
- // 下播总结
- sumup func(ctx context.Context, ptr *c.Common) error
-}
-
-func init() {
- sign := Sign{
- sumup: sumup,
- }
- if e := comp.Put[c.Common](comp.Sign[Sign](`sumup`), sign.sumup); e != nil {
- panic(e)
- }
-}
+// *c.Common
+var Sumup = comp.NewComp(sumup)
func sumup(ctx context.Context, ptr *c.Common) error {
dura := time.Since(ptr.Live_Start_Time).Round(time.Second)
+++ /dev/null
-package reSetMp4TimeStamp
-
-import (
- "context"
- "errors"
- "fmt"
- "io"
- "time"
-
- comp "github.com/qydysky/part/component"
- file "github.com/qydysky/part/file"
-)
-
-// 直接保存下来的mp4在chrome上无法直接播放
-//
-// https://serverfault.com/questions/738881/chrome-makes-way-too-many-requests-22000-while-downloading-mp4-video-34mb
-type Sign struct {
- // 重设mp4的时间戳
- resetTS func(ctx context.Context, ptr *string) error
-}
-
-func init() {
- sign := Sign{
- resetTS: resetTS,
- }
- if e := comp.Put[string](comp.Sign[Sign](`resetTS`), sign.resetTS); e != nil {
- panic(e)
- }
-}
-
-func resetTS(ctx context.Context, ptr *string) error {
- be := time.Now()
- fmt.Println("resetTS")
- defer func() { fmt.Printf("resetTS fin (%v)\n", time.Since(be)) }()
-
- f := file.New(*ptr+"0.mp4", 0, false)
- if !f.IsExist() {
- return nil
- }
- defer f.Close()
-
- var (
- byte4 = make([]byte, 4)
- byte16 = make([]byte, 16)
- bgdts = make(map[int32]int64)
- eddts = make(map[int32]int64)
- zdts = make(map[int32]*guessDts)
- // timescale = make(map[int32]int64)
- )
-
- for {
- if e := f.SeekUntil([]byte("tkhd"), file.AtCurrent, 1<<17, 1<<22); e != nil {
- if errors.Is(e, file.ErrMaxReadSizeReach) {
- break
- }
- if errors.Is(e, io.EOF) {
- break
- }
- return e
- }
- _ = f.SeekIndex(16, file.AtCurrent)
- if _, e := f.Read(byte4); e != nil {
- return e
- }
- trackId := btoi32(byte4, 0)
-
- bgdts[trackId] = -1
- eddts[trackId] = 0
- zdts[trackId] = &guessDts{0, 1}
- }
-
- // _ = f.SeekIndex(0, file.AtOrigin)
- // for {
- // if e := f.SeekUntil([]byte("tkhd"), file.AtCurrent, 1<<17, 1<<22); e != nil {
- // if errors.Is(e, file.ErrMaxReadSizeReach) {
- // break
- // }
- // if errors.Is(e, io.EOF) {
- // break
- // }
- // return e
- // }
- // _ = f.SeekIndex(16, file.AtCurrent)
- // if _, e := f.Read(byte4); e != nil {
- // return e
- // }
- // trackId := btoi32(byte4, 0)
-
- // if e := f.SeekUntil([]byte("mdhd"), file.AtCurrent, 1<<17, 1<<22); e != nil {
- // if errors.Is(e, file.ErrMaxReadSizeReach) {
- // break
- // }
- // if errors.Is(e, io.EOF) {
- // break
- // }
- // return e
- // }
- // _ = f.SeekIndex(16, file.AtCurrent)
- // if _, e := f.Read(byte4); e != nil {
- // return e
- // }
-
- // timescale[trackId] = int64(btoi32(byte4, 0))
- // }
-
- // rewrite dts
- {
- _ = f.SeekIndex(0, file.AtOrigin)
- for {
- if e := f.SeekUntil([]byte("tfhd"), file.AtCurrent, 1<<17, 1<<20); e != nil {
- if errors.Is(e, file.ErrMaxReadSizeReach) {
- continue
- }
- if errors.Is(e, io.EOF) {
- break
- }
- return e
- }
- _ = f.SeekIndex(8, file.AtCurrent)
- if _, e := f.Read(byte4); e != nil {
- return e
- }
- trackID := btoi32(byte4, 0)
-
- if e := f.SeekUntil([]byte("tfdt"), file.AtCurrent, 1<<17, 1<<20); e != nil {
- if errors.Is(e, file.ErrMaxReadSizeReach) {
- continue
- }
- if errors.Is(e, io.EOF) {
- break
- }
- return e
- }
- if _, e := f.Read(byte16); e != nil {
- return e
- }
- switch byte16[4] {
- case 0:
- ts := int64(btoi32(byte16, 12))
- if e := f.SeekIndex(-4, file.AtCurrent); e != nil {
- return e
- }
- if bgdts[trackID] == -1 {
- bgdts[trackID] = ts
- }
- if _, e := f.Write(itob32(int32(ts-bgdts[trackID])), false); e != nil {
- return e
- }
- eddts[trackID] = ts
- case 1:
- ts := btoi64(byte16, 8)
- if e := f.SeekIndex(-8, file.AtCurrent); e != nil {
- return e
- }
- if bgdts[trackID] == -1 {
- bgdts[trackID] = ts
- }
- if _, e := f.Write(itob64(ts-bgdts[trackID]), false); e != nil {
- return e
- }
- eddts[trackID] = ts
- default:
- return fmt.Errorf("unknow tfdt version %x", byte16[8])
- }
- }
- }
-
- // var duration int32
- // // for k, v := range bgdts {
- // // duration = int32((eddts[k] - v) / timescale[k])
- // // break
- // // }
-
- // _ = f.SeekIndex(0, file.AtOrigin)
- // {
- // if e := f.SeekUntil([]byte("moov"), file.AtCurrent, 1<<17, 1<<22); e != nil {
- // return e
- // }
- // }
-
- // // write mvhd
- // _ = f.SeekIndex(0, file.AtOrigin)
- // {
- // if e := f.SeekUntil([]byte("mvhd"), file.AtCurrent, 1<<17, 1<<22); e != nil {
- // return e
- // }
- // _ = f.SeekIndex(20, file.AtCurrent)
- // if _, e := f.Write(itob32(duration), false); e != nil {
- // return e
- // }
- // }
-
- // // write tkhd mdhd
- // _ = f.SeekIndex(0, file.AtOrigin)
- // for {
- // if e := f.SeekUntil([]byte("tkhd"), file.AtCurrent, 1<<17, 1<<20); e != nil {
- // if errors.Is(e, file.ErrMaxReadSizeReach) {
- // break
- // }
- // if errors.Is(e, io.EOF) {
- // break
- // }
- // return e
- // }
- // _ = f.SeekIndex(24, file.AtCurrent)
- // if _, e := f.Write(itob32(duration), false); e != nil {
- // return e
- // }
-
- // if e := f.SeekUntil([]byte("mdhd"), file.AtCurrent, 1<<17, 1<<22); e != nil {
- // if errors.Is(e, file.ErrMaxReadSizeReach) {
- // continue
- // }
- // if errors.Is(e, io.EOF) {
- // break
- // }
- // return e
- // }
- // _ = f.SeekIndex(20, file.AtCurrent)
- // if _, e := f.Write(itob32(duration), false); e != nil {
- // return e
- // }
- // }
- return nil
-}
-
-type guessDts struct {
- zdts int64
- count int64
-}
-
-func btoi64(b []byte, offset int) int64 {
- s := 8
- bu := make([]byte, s)
- l := len(b) - offset
- if l > s {
- l = s
- }
- for i := 0; i < s && i < l; i++ {
- bu[i+s-l] = b[offset+i]
- }
-
- //binary.BigEndian.Uint64
- return int64(uint64(bu[7]) | uint64(bu[6])<<8 | uint64(bu[5])<<16 | uint64(bu[4])<<24 |
- uint64(bu[3])<<32 | uint64(bu[2])<<40 | uint64(bu[1])<<48 | uint64(bu[0])<<56)
-}
-
-func btoi32(b []byte, offset int) int32 {
- s := 4
- bu := make([]byte, s)
- l := len(b) - offset
- if l > s {
- l = s
- }
- for i := 0; i < s && i < l; i++ {
- bu[i+s-l] = b[offset+i]
- }
-
- //binary.BigEndian.Uint32
- return int32((uint32(bu[3]) | uint32(bu[2])<<8 | uint32(bu[1])<<16 | uint32(bu[0])<<24))
-}
-
-func itob64(v int64) []byte {
- //binary.BigEndian.PutUint64
- b := make([]byte, 8)
- b[0] = byte(v >> 56)
- b[1] = byte(v >> 48)
- b[2] = byte(v >> 40)
- b[3] = byte(v >> 32)
- b[4] = byte(v >> 24)
- b[5] = byte(v >> 16)
- b[6] = byte(v >> 8)
- b[7] = byte(v)
- return b
-}
-
-func itob32(v int32) []byte {
- //binary.BigEndian.PutUint32
- b := make([]byte, 4)
- b[0] = byte(v >> 24)
- b[1] = byte(v >> 16)
- b[2] = byte(v >> 8)
- b[3] = byte(v)
- return b
-}
--- /dev/null
+package recStartEnd
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "math"
+ "time"
+
+ c "github.com/qydysky/bili_danmu/CV"
+ comp "github.com/qydysky/part/component"
+ log "github.com/qydysky/part/log"
+ "golang.org/x/exp/slices"
+)
+
+var (
+ InitF = comp.NewComp(initf)
+ RecStartCheck = comp.NewComp(recStartCheck)
+ LoopCheck = comp.NewComp(loopCheck)
+)
+
+type dur struct {
+ start int
+ end int
+}
+
+var (
+ logg *log.Log_interface
+ roomSetting map[int][]dur
+ timePoints []int
+)
+
+func initf(ctx context.Context, ptr *c.Common) (err error) {
+ if list, ok := ptr.K_v.LoadV("指定房间录制区间").([]any); ok {
+ logg = ptr.Log.Base("功能", "指定房间录制区间")
+ defer func() {
+ if err != nil {
+ clear(roomSetting)
+ clear(timePoints)
+ }
+ }()
+ if roomSetting == nil {
+ roomSetting = make(map[int][]dur)
+ }
+ clear(roomSetting)
+ for _, v := range list {
+ if vm, ok := v.(map[string]any); ok {
+ if roomid, ok := vm["roomid"].(float64); ok && int(roomid) > 0 {
+ var durs []dur
+ if sts, ok := vm["fromTo"].([]any); ok {
+ for _, v := range sts {
+ if vm, ok := v.(map[string]any); ok {
+ var durv dur
+ if start, ok := vm["start"].(string); ok {
+ if tt, e := time.Parse(time.TimeOnly, start); e != nil {
+ err = e
+ return
+ } else {
+ durv.start = tt.Hour()*3600 + tt.Minute()*60 + tt.Second() + 1
+ timePoints = append(timePoints, durv.start)
+ }
+ }
+ if end, ok := vm["end"].(string); ok {
+ if tt, e := time.Parse(time.TimeOnly, end); e != nil {
+ err = e
+ return
+ } else {
+ durv.end = tt.Hour()*3600 + tt.Minute()*60 + tt.Second() + 1
+ timePoints = append(timePoints, durv.end)
+ }
+ }
+ durs = append(durs, durv)
+ }
+ }
+ }
+ logg.L(`T: `, "加载规则", fmt.Sprintf("%d %d条", int(roomid), len(durs)))
+ roomSetting[int(roomid)] = durs
+ }
+ }
+ }
+ slices.Sort(timePoints)
+ }
+ return nil
+}
+
+func recStartCheck(ctx context.Context, ptr *c.Common) error {
+ if setting, ok := roomSetting[ptr.Roomid]; ok {
+ now := time.Now()
+ t := now.Hour()*3600 + now.Minute()*60 + now.Second() + 1
+ for _, v := range setting {
+ if v.start != 0 && v.end != 0 && t <= v.end && t >= v.start {
+ return nil
+ }
+ }
+ return errors.New("当前不在设定时间段内")
+ }
+ return nil
+}
+
+type StreamCtl struct {
+ C *c.Common
+ State func(int) bool
+ Start func(int)
+ End func(int)
+ Cut func(int)
+}
+
+var streamCtl StreamCtl
+
+func loopCheck(ctx context.Context, ptr StreamCtl) error {
+ streamCtl = ptr
+ setNextFunc()
+ return nil
+}
+
+func setNextFunc() {
+ if len(timePoints) == 0 {
+ return
+ }
+
+ now := time.Now()
+ t := now.Hour()*3600 + now.Minute()*60 + now.Second() + 1
+
+ var tmp []int
+ for i := 0; i < len(timePoints); i++ {
+ if t > timePoints[i] {
+ tmp = append(tmp, timePoints[i]+60*60*24-t)
+ } else {
+ tmp = append(tmp, timePoints[i]-t)
+ }
+ }
+ slices.Sort(tmp)
+
+ // logg.L(`T: `, "下个时间点", time.Now().Add(time.Second*time.Duration(tmp[0])).Format(time.DateTime))
+
+ time.AfterFunc(time.Second*time.Duration(tmp[0]), func() {
+ if streamCtl.C.Liveing {
+ if setting, ok := roomSetting[streamCtl.C.Roomid]; ok {
+ now := time.Now()
+ t := now.Hour()*3600 + now.Minute()*60 + now.Second() + 1
+ for _, v := range setting {
+ if v.start != 0 && math.Abs(float64(t-v.start)) < 5 {
+ if streamCtl.State(streamCtl.C.Roomid) {
+ logg.L(`T: `, "切片", streamCtl.C.Roomid)
+ streamCtl.Cut(streamCtl.C.Roomid)
+ } else {
+ logg.L(`T: `, "开始", streamCtl.C.Roomid)
+ streamCtl.Start(streamCtl.C.Roomid)
+ }
+ time.Sleep(time.Second * 5)
+ break
+ }
+ if v.end != 0 && math.Abs(float64(t-v.end)) < 5 {
+ if streamCtl.State(streamCtl.C.Roomid) {
+ logg.L(`T: `, "结束", streamCtl.C.Roomid)
+ streamCtl.End(streamCtl.C.Roomid)
+ }
+ time.Sleep(time.Second * 5)
+ break
+ }
+ }
+ }
+ }
+ setNextFunc()
+ })
+}
brotli "github.com/andybalholm/brotli"
c "github.com/qydysky/bili_danmu/CV"
F "github.com/qydysky/bili_danmu/F"
+ "github.com/qydysky/bili_danmu/Reply/F/liveOver"
+ "github.com/qydysky/bili_danmu/Reply/F/recStartEnd"
ws_msg "github.com/qydysky/bili_danmu/Reply/ws_msg"
send "github.com/qydysky/bili_danmu/Send"
p "github.com/qydysky/part"
- comp "github.com/qydysky/part/component"
mq "github.com/qydysky/part/msgq"
pstrings "github.com/qydysky/part/strings"
)
StreamOStop(roomId)
// 下播总结
type empty struct{}
- if e := comp.Run(comp.Sign[empty](`preparing`), context.Background(), c.C); e != nil {
+ if e := liveOver.Sumup.Run(context.Background(), c.C); e != nil {
msglog.L(`E: `, e)
}
}
if v, ok := c.C.K_v.LoadV(`仅保存当前直播间流`).(bool); ok && v {
StreamOStop(-2) //停止其他房间录制
}
- StreamOStart(c.C.Roomid)
+ if e := recStartEnd.RecStartCheck.Run(context.Background(), c.C); e == nil {
+ if StreamOStatus(c.C.Roomid) {
+ StreamOCut(c.C.Roomid)
+ } else {
+ StreamOStart(c.C.Roomid)
+ }
+ } else {
+ msglog.L(`W: `, "房间", type_item.Roomid, e)
+ }
//有时不返回弹幕 开播刷新弹幕
c.C.Danmu_Main_mq.Push_tag(`flash_room`, nil)
}()
c "github.com/qydysky/bili_danmu/CV"
F "github.com/qydysky/bili_danmu/F"
reply "github.com/qydysky/bili_danmu/Reply"
+ "github.com/qydysky/bili_danmu/Reply/F/recStartEnd"
send "github.com/qydysky/bili_danmu/Send"
Cmd "github.com/qydysky/bili_danmu/cmd"
sys "github.com/qydysky/part/sys"
F.Dosign()
// 附加功能 savetojson
reply.SaveToJson.Init()
+ // 指定房间录制区间
+ if err := recStartEnd.InitF.Run(context.Background(), c.C); err != nil {
+ danmulog.Base("功能", "指定房间录制区间").L(`E: `, err)
+ }
//使用带tag的消息队列在功能间传递消息
{
reply.Danmuji_auto()
}
{ //附加功能 进房间发送弹幕 直播流保存 每日签到
+ _ = recStartEnd.LoopCheck.Run(context.Background(), recStartEnd.StreamCtl{
+ C: c.C,
+ State: reply.StreamOStatus,
+ Start: reply.StreamOStart,
+ End: reply.StreamOStop,
+ Cut: func(i int) { reply.StreamOCut(i) },
+ })
go F.Dosign()
go reply.Entry_danmu()
- go reply.StreamOStart(c.C.Roomid)
+ if e := recStartEnd.RecStartCheck.Run(context.Background(), c.C); e == nil {
+ go reply.StreamOStart(c.C.Roomid)
+ } else {
+ danmulog.Base("功能", "指定房间录制区间").L(`I: `, c.C.Roomid, e)
+ }
go F.RoomEntryAction(c.C.Roomid)
}
"after":["ffmpeg","-i","0.{type}","-y","-c","copy","1.{type}"]
}
],
+ "指定房间录制区间-help":"指定roomid的房间在指定时间段内将会开启录制.start时检查是否在直播,是则开始录制,如已在录制则切片.end时停止录制.在开播时,若在start与end之间,则录制,5s内只能触发一个fromTo",
+ "指定房间录制区间":[
+ {
+ "roomid":0,
+ "fromTo":[
+ {
+ "start": "12:01:00",
+ "end": "12:03:00"
+ },
+ {
+ "start": "12:02:00"
+ }
+ ]
+ }
+ ],
"Web服务地址-help":"填写本程序各组件所用的服务地址 例0.0.0.0:10000 为空时不启动Web服务",
"Web服务地址":"0.0.0.0:10000",
"Web服务连接限制-help": "限制回放连接数,<0无限制,=0禁止,>0最大数量",
require (
github.com/gotk3/gotk3 v0.6.2
github.com/mdp/qrterminal/v3 v3.2.0
- github.com/qydysky/part v0.28.1-0.20231109160627-cb7ab257995b
+ github.com/qydysky/part v0.28.1-0.20231118041002-d67071eadd31
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.14.0
github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/qydysky/part v0.28.1-0.20231109160627-cb7ab257995b h1:nKmP2PJdgpFBVWl9O92zKNS9k7MgBGXUGH0jdXpd7Xo=
-github.com/qydysky/part v0.28.1-0.20231109160627-cb7ab257995b/go.mod h1:twb1IuSmUJ3hllGLwWTBjXRkHjsgmiYi3B9H2ENgIf0=
+github.com/qydysky/part v0.28.1-0.20231118041002-d67071eadd31 h1:JDZ3yCv4bO1WQP05Le1KwhQL4uopNASbmb6Aovr9yd4=
+github.com/qydysky/part v0.28.1-0.20231118041002-d67071eadd31/go.mod h1:NyKyjpBCSjcHtKlC+fL5lCidm57UCnwEgufiBDs5yxA=
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=