mirror of
https://github.com/langhuihui/monibuca.git
synced 2025-12-24 13:48:04 +08:00
feat: record recover
This commit is contained in:
44
api.go
44
api.go
@@ -551,32 +551,26 @@ func (s *Server) StopPublish(ctx context.Context, req *pb.StreamSnapRequest) (re
|
||||
// /api/stream/list
|
||||
func (s *Server) StreamList(_ context.Context, req *pb.StreamListRequest) (res *pb.StreamListResponse, err error) {
|
||||
recordingMap := make(map[string][]*pb.RecordingDetail)
|
||||
s.Records.Call(func() error {
|
||||
for record := range s.Records.Range {
|
||||
recordingMap[record.StreamPath] = append(recordingMap[record.StreamPath], &pb.RecordingDetail{
|
||||
FilePath: record.RecConf.FilePath,
|
||||
Mode: record.Mode,
|
||||
Fragment: durationpb.New(record.RecConf.Fragment),
|
||||
Append: record.RecConf.Append,
|
||||
PluginName: record.Plugin.Meta.Name,
|
||||
Pointer: uint64(record.GetTaskPointer()),
|
||||
})
|
||||
for record := range s.Records.SafeRange {
|
||||
recordingMap[record.StreamPath] = append(recordingMap[record.StreamPath], &pb.RecordingDetail{
|
||||
FilePath: record.RecConf.FilePath,
|
||||
Mode: record.Mode,
|
||||
Fragment: durationpb.New(record.RecConf.Fragment),
|
||||
Append: record.RecConf.Append,
|
||||
PluginName: record.Plugin.Meta.Name,
|
||||
Pointer: uint64(record.GetTaskPointer()),
|
||||
})
|
||||
}
|
||||
var streams []*pb.StreamInfo
|
||||
for publisher := range s.Streams.SafeRange {
|
||||
info, err := s.getStreamInfo(publisher)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
return nil
|
||||
})
|
||||
s.Streams.Call(func() error {
|
||||
var streams []*pb.StreamInfo
|
||||
for publisher := range s.Streams.Range {
|
||||
info, err := s.getStreamInfo(publisher)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
info.Data.Recording = recordingMap[info.Data.Path]
|
||||
streams = append(streams, info.Data)
|
||||
}
|
||||
res = &pb.StreamListResponse{Data: streams, Total: int32(s.Streams.Length), PageNum: req.PageNum, PageSize: req.PageSize}
|
||||
return nil
|
||||
})
|
||||
info.Data.Recording = recordingMap[info.Data.Path]
|
||||
streams = append(streams, info.Data)
|
||||
}
|
||||
res = &pb.StreamListResponse{Data: streams, Total: int32(s.Streams.Length), PageNum: req.PageNum, PageSize: req.PageSize}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -37,9 +37,27 @@
|
||||
| | | | | | sbgp | | sample-to-group |
|
||||
| | | | | | sgpd | | sample group description |
|
||||
| | | | | | subs | | sub-sample information |
|
||||
| | | udta | | | | | user-data (track level)<br>轨道级别的用户数据容器 |
|
||||
| | | | cprt | | | | copyright etc.<br>版权信息 |
|
||||
| | | | titl | | | | title<br>标题 |
|
||||
| | | | auth | | | | author<br>作者 |
|
||||
| | mvex | | | | | | movie extends box |
|
||||
| | | mehd | | | | | movie extends header box |
|
||||
| | | trex | | | | ✓ | track extends defaults |
|
||||
| | udta | | | | | | user-data (movie level)<br>电影级别的用户数据容器 |
|
||||
| | | cprt | | | | | copyright etc.<br>版权信息 |
|
||||
| | | titl | | | | | title<br>标题 |
|
||||
| | | auth | | | | | author<br>作者 |
|
||||
| | | albm | | | | | album<br>专辑 |
|
||||
| | | yrrc | | | | | year<br>年份 |
|
||||
| | | rtng | | | | | rating<br>评级 |
|
||||
| | | clsf | | | | | classification<br>分类 |
|
||||
| | | kywd | | | | | keywords<br>关键词 |
|
||||
| | | loci | | | | | location information<br>位置信息 |
|
||||
| | | dscp | | | | | description<br>描述 |
|
||||
| | | perf | | | | | performer<br>表演者 |
|
||||
| | | gnre | | | | | genre<br>类型 |
|
||||
| | | meta | | | | | metadata atom<br>元数据原子 |
|
||||
| | ipmc | | | | | | IPMP Control Box |
|
||||
| moof | | | | | | | movie fragment |
|
||||
| | mfhd | | | | | ✓ | movie fragment header |
|
||||
@@ -54,8 +72,10 @@
|
||||
| mdat | | | | | | | media data container |
|
||||
| free | | | | | | | free space |
|
||||
| skip | | | | | | | free space |
|
||||
| | udta | | | | | | user-data |
|
||||
| | | cprt | | | | | copyright etc. |
|
||||
| udta | | | | | | | user-data (file level)<br>文件级别的用户数据容器 |
|
||||
| | cprt | | | | | | copyright etc.<br>版权信息 |
|
||||
| | titl | | | | | | title<br>标题 |
|
||||
| | auth | | | | | | author<br>作者 |
|
||||
| meta | | | | | | | metadata |
|
||||
| | hdlr | | | | | ✓ | handler, declares the metadata (handler) type |
|
||||
| | dinf | | | | | | data information box, container |
|
||||
|
||||
@@ -343,7 +343,25 @@ var (
|
||||
TypeAUXV = f("auxv")
|
||||
TypeHINT = f("hint")
|
||||
TypeUDTA = f("udta")
|
||||
TypeM7SP = f("m7sp") // Custom box type for M7S StreamPath
|
||||
|
||||
// Common metadata box types
|
||||
TypeTITL = f("©nam") // Title
|
||||
TypeART = f("©ART") // Artist/Author
|
||||
TypeALB = f("©alb") // Album
|
||||
TypeDAY = f("©day") // Date/Year
|
||||
TypeCMT = f("©cmt") // Comment/Description
|
||||
TypeGEN = f("©gen") // Genre
|
||||
TypeCPRT = f("cprt") // Copyright
|
||||
TypeENCO = f("©too") // Encoder/Tool
|
||||
TypeWRT = f("©wrt") // Writer/Composer
|
||||
TypePRD = f("©prd") // Producer
|
||||
TypePRF = f("©prf") // Performer
|
||||
TypeGRP = f("©grp") // Grouping
|
||||
TypeLYR = f("©lyr") // Lyrics
|
||||
TypeKEYW = f("keyw") // Keywords
|
||||
TypeLOCI = f("loci") // Location Information
|
||||
TypeRTNG = f("rtng") // Rating
|
||||
TypeMETA_CUST = f("----") // Custom metadata (iTunes-style)
|
||||
)
|
||||
|
||||
// aligned(8) class Box (unsigned int(32) boxtype, optional unsigned int(8)[16] extended_type) {
|
||||
|
||||
334
plugin/mp4/pkg/box/metadata.go
Normal file
334
plugin/mp4/pkg/box/metadata.go
Normal file
@@ -0,0 +1,334 @@
|
||||
package box
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Metadata holds various metadata information for MP4
|
||||
type Metadata struct {
|
||||
Title string // 标题
|
||||
Artist string // 艺术家/作者
|
||||
Album string // 专辑
|
||||
Date string // 日期
|
||||
Comment string // 注释/描述
|
||||
Genre string // 类型
|
||||
Copyright string // 版权信息
|
||||
Encoder string // 编码器
|
||||
Writer string // 作词者
|
||||
Producer string // 制作人
|
||||
Performer string // 表演者
|
||||
Grouping string // 分组
|
||||
Lyrics string // 歌词
|
||||
Keywords string // 关键词
|
||||
Location string // 位置信息
|
||||
Rating uint8 // 评级 (0-5)
|
||||
Custom map[string]string // 自定义键值对
|
||||
}
|
||||
|
||||
// Text Data Box - for storing text metadata
|
||||
type TextDataBox struct {
|
||||
FullBox
|
||||
Text string
|
||||
}
|
||||
|
||||
// Metadata Data Box - for storing binary metadata with type indicator
|
||||
type MetadataDataBox struct {
|
||||
FullBox
|
||||
DataType uint32 // Data type indicator
|
||||
Country uint32 // Country code
|
||||
Language uint32 // Language code
|
||||
Data []byte // Actual data
|
||||
}
|
||||
|
||||
// Copyright Box
|
||||
type CopyrightBox struct {
|
||||
FullBox
|
||||
Language [3]byte
|
||||
Notice string
|
||||
}
|
||||
|
||||
// Custom Metadata Box (iTunes-style ---- box)
|
||||
type CustomMetadataBox struct {
|
||||
BaseBox
|
||||
Mean string // Mean (namespace)
|
||||
Name string // Name (key)
|
||||
Data []byte // Data
|
||||
}
|
||||
|
||||
// Create functions
|
||||
|
||||
func CreateTextDataBox(boxType BoxType, text string) *TextDataBox {
|
||||
return &TextDataBox{
|
||||
FullBox: FullBox{
|
||||
BaseBox: BaseBox{
|
||||
typ: boxType,
|
||||
size: uint32(FullBoxLen + len(text)),
|
||||
},
|
||||
Version: 0,
|
||||
Flags: [3]byte{0, 0, 0},
|
||||
},
|
||||
Text: text,
|
||||
}
|
||||
}
|
||||
|
||||
func CreateMetadataDataBox(dataType uint32, data []byte) *MetadataDataBox {
|
||||
return &MetadataDataBox{
|
||||
FullBox: FullBox{
|
||||
BaseBox: BaseBox{
|
||||
typ: f("data"),
|
||||
size: uint32(FullBoxLen + 8 + len(data)), // 8 bytes for type+country+language
|
||||
},
|
||||
Version: 0,
|
||||
Flags: [3]byte{0, 0, 0},
|
||||
},
|
||||
DataType: dataType,
|
||||
Country: 0,
|
||||
Language: 0,
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
func CreateCopyrightBox(language [3]byte, notice string) *CopyrightBox {
|
||||
return &CopyrightBox{
|
||||
FullBox: FullBox{
|
||||
BaseBox: BaseBox{
|
||||
typ: TypeCPRT,
|
||||
size: uint32(FullBoxLen + 3 + 1 + len(notice)), // 3 for language, 1 for null terminator
|
||||
},
|
||||
Version: 0,
|
||||
Flags: [3]byte{0, 0, 0},
|
||||
},
|
||||
Language: language,
|
||||
Notice: notice,
|
||||
}
|
||||
}
|
||||
|
||||
func CreateCustomMetadataBox(mean, name string, data []byte) *CustomMetadataBox {
|
||||
size := uint32(BasicBoxLen + 4 + len(mean) + 4 + len(name) + len(data))
|
||||
return &CustomMetadataBox{
|
||||
BaseBox: BaseBox{
|
||||
typ: TypeMETA_CUST,
|
||||
size: size,
|
||||
},
|
||||
Mean: mean,
|
||||
Name: name,
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
// WriteTo methods
|
||||
|
||||
func (box *TextDataBox) WriteTo(w io.Writer) (n int64, err error) {
|
||||
nn, err := w.Write([]byte(box.Text))
|
||||
return int64(nn), err
|
||||
}
|
||||
|
||||
func (box *MetadataDataBox) WriteTo(w io.Writer) (n int64, err error) {
|
||||
var tmp [8]byte
|
||||
binary.BigEndian.PutUint32(tmp[0:4], box.DataType)
|
||||
binary.BigEndian.PutUint32(tmp[4:8], box.Country)
|
||||
// Language field is implicit zero
|
||||
|
||||
nn, err := w.Write(tmp[:8])
|
||||
if err != nil {
|
||||
return int64(nn), err
|
||||
}
|
||||
n = int64(nn)
|
||||
|
||||
nn, err = w.Write(box.Data)
|
||||
return n + int64(nn), err
|
||||
}
|
||||
|
||||
func (box *CopyrightBox) WriteTo(w io.Writer) (n int64, err error) {
|
||||
// Write language code
|
||||
nn, err := w.Write(box.Language[:])
|
||||
if err != nil {
|
||||
return int64(nn), err
|
||||
}
|
||||
n = int64(nn)
|
||||
|
||||
// Write notice + null terminator
|
||||
nn, err = w.Write([]byte(box.Notice + "\x00"))
|
||||
return n + int64(nn), err
|
||||
}
|
||||
|
||||
func (box *CustomMetadataBox) WriteTo(w io.Writer) (n int64, err error) {
|
||||
var tmp [4]byte
|
||||
|
||||
// Write mean length + mean
|
||||
binary.BigEndian.PutUint32(tmp[:], uint32(len(box.Mean)))
|
||||
nn, err := w.Write(tmp[:])
|
||||
if err != nil {
|
||||
return int64(nn), err
|
||||
}
|
||||
n = int64(nn)
|
||||
|
||||
nn, err = w.Write([]byte(box.Mean))
|
||||
if err != nil {
|
||||
return n + int64(nn), err
|
||||
}
|
||||
n += int64(nn)
|
||||
|
||||
// Write name length + name
|
||||
binary.BigEndian.PutUint32(tmp[:], uint32(len(box.Name)))
|
||||
nn, err = w.Write(tmp[:])
|
||||
if err != nil {
|
||||
return n + int64(nn), err
|
||||
}
|
||||
n += int64(nn)
|
||||
|
||||
nn, err = w.Write([]byte(box.Name))
|
||||
if err != nil {
|
||||
return n + int64(nn), err
|
||||
}
|
||||
n += int64(nn)
|
||||
|
||||
// Write data
|
||||
nn, err = w.Write(box.Data)
|
||||
return n + int64(nn), err
|
||||
}
|
||||
|
||||
// Unmarshal methods
|
||||
|
||||
func (box *TextDataBox) Unmarshal(buf []byte) (IBox, error) {
|
||||
box.Text = string(buf)
|
||||
return box, nil
|
||||
}
|
||||
|
||||
func (box *MetadataDataBox) Unmarshal(buf []byte) (IBox, error) {
|
||||
if len(buf) < 8 {
|
||||
return nil, io.ErrShortBuffer
|
||||
}
|
||||
|
||||
box.DataType = binary.BigEndian.Uint32(buf[0:4])
|
||||
box.Country = binary.BigEndian.Uint32(buf[4:8])
|
||||
box.Data = buf[8:]
|
||||
return box, nil
|
||||
}
|
||||
|
||||
func (box *CopyrightBox) Unmarshal(buf []byte) (IBox, error) {
|
||||
if len(buf) < 4 {
|
||||
return nil, io.ErrShortBuffer
|
||||
}
|
||||
|
||||
copy(box.Language[:], buf[0:3])
|
||||
// Find null terminator
|
||||
for i := 3; i < len(buf); i++ {
|
||||
if buf[i] == 0 {
|
||||
box.Notice = string(buf[3:i])
|
||||
break
|
||||
}
|
||||
}
|
||||
if box.Notice == "" && len(buf) > 3 {
|
||||
box.Notice = string(buf[3:])
|
||||
}
|
||||
return box, nil
|
||||
}
|
||||
|
||||
func (box *CustomMetadataBox) Unmarshal(buf []byte) (IBox, error) {
|
||||
if len(buf) < 8 {
|
||||
return nil, io.ErrShortBuffer
|
||||
}
|
||||
|
||||
offset := 0
|
||||
|
||||
// Read mean length + mean
|
||||
meanLen := binary.BigEndian.Uint32(buf[offset:])
|
||||
offset += 4
|
||||
if offset+int(meanLen) > len(buf) {
|
||||
return nil, io.ErrShortBuffer
|
||||
}
|
||||
box.Mean = string(buf[offset : offset+int(meanLen)])
|
||||
offset += int(meanLen)
|
||||
|
||||
// Read name length + name
|
||||
if offset+4 > len(buf) {
|
||||
return nil, io.ErrShortBuffer
|
||||
}
|
||||
nameLen := binary.BigEndian.Uint32(buf[offset:])
|
||||
offset += 4
|
||||
if offset+int(nameLen) > len(buf) {
|
||||
return nil, io.ErrShortBuffer
|
||||
}
|
||||
box.Name = string(buf[offset : offset+int(nameLen)])
|
||||
offset += int(nameLen)
|
||||
|
||||
// Read remaining data
|
||||
box.Data = buf[offset:]
|
||||
return box, nil
|
||||
}
|
||||
|
||||
// Create metadata entries from Metadata struct
|
||||
func CreateMetadataEntries(metadata *Metadata) []IBox {
|
||||
var entries []IBox
|
||||
|
||||
// Standard text metadata
|
||||
if metadata.Title != "" {
|
||||
entries = append(entries, CreateTextDataBox(TypeTITL, metadata.Title))
|
||||
}
|
||||
if metadata.Artist != "" {
|
||||
entries = append(entries, CreateTextDataBox(TypeART, metadata.Artist))
|
||||
}
|
||||
if metadata.Album != "" {
|
||||
entries = append(entries, CreateTextDataBox(TypeALB, metadata.Album))
|
||||
}
|
||||
if metadata.Date != "" {
|
||||
entries = append(entries, CreateTextDataBox(TypeDAY, metadata.Date))
|
||||
}
|
||||
if metadata.Comment != "" {
|
||||
entries = append(entries, CreateTextDataBox(TypeCMT, metadata.Comment))
|
||||
}
|
||||
if metadata.Genre != "" {
|
||||
entries = append(entries, CreateTextDataBox(TypeGEN, metadata.Genre))
|
||||
}
|
||||
if metadata.Encoder != "" {
|
||||
entries = append(entries, CreateTextDataBox(TypeENCO, metadata.Encoder))
|
||||
}
|
||||
if metadata.Writer != "" {
|
||||
entries = append(entries, CreateTextDataBox(TypeWRT, metadata.Writer))
|
||||
}
|
||||
if metadata.Producer != "" {
|
||||
entries = append(entries, CreateTextDataBox(TypePRD, metadata.Producer))
|
||||
}
|
||||
if metadata.Performer != "" {
|
||||
entries = append(entries, CreateTextDataBox(TypePRF, metadata.Performer))
|
||||
}
|
||||
if metadata.Grouping != "" {
|
||||
entries = append(entries, CreateTextDataBox(TypeGRP, metadata.Grouping))
|
||||
}
|
||||
if metadata.Lyrics != "" {
|
||||
entries = append(entries, CreateTextDataBox(TypeLYR, metadata.Lyrics))
|
||||
}
|
||||
if metadata.Keywords != "" {
|
||||
entries = append(entries, CreateTextDataBox(TypeKEYW, metadata.Keywords))
|
||||
}
|
||||
if metadata.Location != "" {
|
||||
entries = append(entries, CreateTextDataBox(TypeLOCI, metadata.Location))
|
||||
}
|
||||
|
||||
// Copyright (special format)
|
||||
if metadata.Copyright != "" {
|
||||
entries = append(entries, CreateCopyrightBox([3]byte{'u', 'n', 'd'}, metadata.Copyright))
|
||||
}
|
||||
|
||||
// Custom metadata
|
||||
for key, value := range metadata.Custom {
|
||||
entries = append(entries, CreateCustomMetadataBox("live.m7s.custom", key, []byte(value)))
|
||||
}
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
// Helper function to create current date string
|
||||
func GetCurrentDateString() string {
|
||||
return time.Now().Format("2006-01-02")
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterBox[*TextDataBox](TypeTITL, TypeART, TypeALB, TypeDAY, TypeCMT, TypeGEN, TypeENCO, TypeWRT, TypePRD, TypePRF, TypeGRP, TypeLYR, TypeKEYW, TypeLOCI, TypeRTNG)
|
||||
RegisterBox[*MetadataDataBox](f("data"))
|
||||
RegisterBox[*CopyrightBox](TypeCPRT)
|
||||
RegisterBox[*CustomMetadataBox](TypeMETA_CUST)
|
||||
}
|
||||
@@ -12,12 +12,6 @@ type UserDataBox struct {
|
||||
Entries []IBox
|
||||
}
|
||||
|
||||
// Custom metadata box for storing stream path
|
||||
type StreamPathBox struct {
|
||||
FullBox
|
||||
StreamPath string
|
||||
}
|
||||
|
||||
// Create a new User Data Box
|
||||
func CreateUserDataBox(entries ...IBox) *UserDataBox {
|
||||
size := uint32(BasicBoxLen)
|
||||
@@ -33,21 +27,6 @@ func CreateUserDataBox(entries ...IBox) *UserDataBox {
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new StreamPath Box
|
||||
func CreateStreamPathBox(streamPath string) *StreamPathBox {
|
||||
return &StreamPathBox{
|
||||
FullBox: FullBox{
|
||||
BaseBox: BaseBox{
|
||||
typ: TypeM7SP, // Custom box type for M7S StreamPath
|
||||
size: uint32(FullBoxLen + len(streamPath)),
|
||||
},
|
||||
Version: 0,
|
||||
Flags: [3]byte{0, 0, 0},
|
||||
},
|
||||
StreamPath: streamPath,
|
||||
}
|
||||
}
|
||||
|
||||
// WriteTo writes the UserDataBox to the given writer
|
||||
func (box *UserDataBox) WriteTo(w io.Writer) (n int64, err error) {
|
||||
return WriteTo(w, box.Entries...)
|
||||
@@ -69,19 +48,6 @@ func (box *UserDataBox) Unmarshal(buf []byte) (IBox, error) {
|
||||
return box, nil
|
||||
}
|
||||
|
||||
// WriteTo writes the StreamPathBox to the given writer
|
||||
func (box *StreamPathBox) WriteTo(w io.Writer) (n int64, err error) {
|
||||
nn, err := w.Write([]byte(box.StreamPath))
|
||||
return int64(nn), err
|
||||
}
|
||||
|
||||
// Unmarshal parses the given buffer into a StreamPathBox
|
||||
func (box *StreamPathBox) Unmarshal(buf []byte) (IBox, error) {
|
||||
box.StreamPath = string(buf)
|
||||
return box, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterBox[*UserDataBox](TypeUDTA)
|
||||
RegisterBox[*StreamPathBox](TypeM7SP)
|
||||
}
|
||||
|
||||
@@ -29,7 +29,8 @@ type (
|
||||
moov IBox
|
||||
mdatOffset uint64
|
||||
mdatSize uint64
|
||||
StreamPath string // Added to store the stream path
|
||||
StreamPath string // Added to store the stream path
|
||||
Metadata *Metadata // 添加元数据支持
|
||||
}
|
||||
)
|
||||
|
||||
@@ -52,6 +53,7 @@ func NewMuxer(flag Flag) *Muxer {
|
||||
Tracks: make(map[uint32]*Track),
|
||||
Flag: flag,
|
||||
fragDuration: 2000,
|
||||
Metadata: &Metadata{Custom: make(map[string]string)},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +61,8 @@ func NewMuxer(flag Flag) *Muxer {
|
||||
func NewMuxerWithStreamPath(flag Flag, streamPath string) *Muxer {
|
||||
muxer := NewMuxer(flag)
|
||||
muxer.StreamPath = streamPath
|
||||
muxer.Metadata.Producer = "M7S Live"
|
||||
muxer.Metadata.Album = streamPath
|
||||
return muxer
|
||||
}
|
||||
|
||||
@@ -232,10 +236,10 @@ func (m *Muxer) MakeMoov() IBox {
|
||||
children = append(children, m.makeMvex())
|
||||
}
|
||||
|
||||
// Add user data box with stream path if available
|
||||
if m.StreamPath != "" {
|
||||
streamPathBox := CreateStreamPathBox(m.StreamPath)
|
||||
udta := CreateUserDataBox(streamPathBox)
|
||||
// Add user data box with metadata if available
|
||||
metadataEntries := CreateMetadataEntries(m.Metadata)
|
||||
if len(metadataEntries) > 0 {
|
||||
udta := CreateUserDataBox(metadataEntries...)
|
||||
children = append(children, udta)
|
||||
}
|
||||
|
||||
@@ -365,3 +369,82 @@ func (m *Muxer) WriteTrailer(file *os.File) (err error) {
|
||||
func (m *Muxer) SetFragmentDuration(duration uint32) {
|
||||
m.fragDuration = duration
|
||||
}
|
||||
|
||||
// SetMetadata sets the metadata for the MP4 file
|
||||
func (m *Muxer) SetMetadata(metadata *Metadata) {
|
||||
m.Metadata = metadata
|
||||
if metadata.Custom == nil {
|
||||
metadata.Custom = make(map[string]string)
|
||||
}
|
||||
}
|
||||
|
||||
// SetTitle sets the title metadata
|
||||
func (m *Muxer) SetTitle(title string) {
|
||||
m.Metadata.Title = title
|
||||
}
|
||||
|
||||
// SetArtist sets the artist/author metadata
|
||||
func (m *Muxer) SetArtist(artist string) {
|
||||
m.Metadata.Artist = artist
|
||||
}
|
||||
|
||||
// SetAlbum sets the album metadata
|
||||
func (m *Muxer) SetAlbum(album string) {
|
||||
m.Metadata.Album = album
|
||||
}
|
||||
|
||||
// SetComment sets the comment/description metadata
|
||||
func (m *Muxer) SetComment(comment string) {
|
||||
m.Metadata.Comment = comment
|
||||
}
|
||||
|
||||
// SetGenre sets the genre metadata
|
||||
func (m *Muxer) SetGenre(genre string) {
|
||||
m.Metadata.Genre = genre
|
||||
}
|
||||
|
||||
// SetCopyright sets the copyright metadata
|
||||
func (m *Muxer) SetCopyright(copyright string) {
|
||||
m.Metadata.Copyright = copyright
|
||||
}
|
||||
|
||||
// SetEncoder sets the encoder metadata
|
||||
func (m *Muxer) SetEncoder(encoder string) {
|
||||
m.Metadata.Encoder = encoder
|
||||
}
|
||||
|
||||
// SetDate sets the date metadata (format: YYYY-MM-DD)
|
||||
func (m *Muxer) SetDate(date string) {
|
||||
m.Metadata.Date = date
|
||||
}
|
||||
|
||||
// SetCurrentDate sets the date metadata to current date
|
||||
func (m *Muxer) SetCurrentDate() {
|
||||
m.Metadata.Date = GetCurrentDateString()
|
||||
}
|
||||
|
||||
// AddCustomMetadata adds custom key-value metadata
|
||||
func (m *Muxer) AddCustomMetadata(key, value string) {
|
||||
if m.Metadata.Custom == nil {
|
||||
m.Metadata.Custom = make(map[string]string)
|
||||
}
|
||||
m.Metadata.Custom[key] = value
|
||||
}
|
||||
|
||||
// SetKeywords sets the keywords metadata
|
||||
func (m *Muxer) SetKeywords(keywords string) {
|
||||
m.Metadata.Keywords = keywords
|
||||
}
|
||||
|
||||
// SetLocation sets the location metadata
|
||||
func (m *Muxer) SetLocation(location string) {
|
||||
m.Metadata.Location = location
|
||||
}
|
||||
|
||||
// SetRating sets the rating metadata (0-5)
|
||||
func (m *Muxer) SetRating(rating uint8) {
|
||||
if rating > 5 {
|
||||
rating = 5
|
||||
}
|
||||
m.Metadata.Rating = rating
|
||||
}
|
||||
|
||||
@@ -220,8 +220,8 @@ func extractStreamPathFromMP4(demuxer *mp4.Demuxer) string {
|
||||
moov := demuxer.GetMoovBox()
|
||||
if moov != nil && moov.UDTA != nil {
|
||||
for _, entry := range moov.UDTA.Entries {
|
||||
if streamPathBox, ok := entry.(*box.StreamPathBox); ok {
|
||||
return streamPathBox.StreamPath
|
||||
if entry.Type() == box.TypeALB {
|
||||
return entry.(*box.TextDataBox).Text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -582,12 +582,9 @@ func (s *Server) Dispose() {
|
||||
|
||||
func (s *Server) GetPublisher(streamPath string) (publisher *Publisher, err error) {
|
||||
var ok bool
|
||||
s.Streams.Call(func() error {
|
||||
publisher, ok = s.Streams.Get(streamPath)
|
||||
return nil
|
||||
})
|
||||
publisher, ok = s.Streams.SafeGet(streamPath)
|
||||
if !ok {
|
||||
err = fmt.Errorf("src stream not found")
|
||||
err = pkg.ErrNotFound
|
||||
return
|
||||
}
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user