Files
donut/internal/controllers/probers/libav_ffmpeg.go
Leandro Moreira 199c23e9fd add draft for rtmp
2024-05-12 15:05:35 -03:00

113 lines
3.1 KiB
Go

package probers
import (
"fmt"
"strings"
"github.com/asticode/go-astiav"
"github.com/asticode/go-astikit"
"github.com/flavioribeiro/donut/internal/entities"
"github.com/flavioribeiro/donut/internal/mapper"
"go.uber.org/fx"
"go.uber.org/zap"
)
type LibAVFFmpeg struct {
c *entities.Config
l *zap.SugaredLogger
m *mapper.Mapper
}
type ResultLibAVFFmpeg struct {
fx.Out
LibAVFFmpegProber DonutProber `group:"probers"`
}
// NewLibAVFFmpeg creates a new LibAVFFmpeg DonutProber
func NewLibAVFFmpeg(
c *entities.Config,
l *zap.SugaredLogger,
m *mapper.Mapper,
) ResultLibAVFFmpeg {
return ResultLibAVFFmpeg{
LibAVFFmpegProber: &LibAVFFmpeg{
c: c,
l: l,
m: m,
},
}
}
// Match returns true when the request is for an LibAVFFmpeg prober
func (c *LibAVFFmpeg) Match(req *entities.RequestParams) bool {
isRTMP := strings.Contains(strings.ToLower(req.StreamURL), "rtmp")
isSRT := strings.Contains(strings.ToLower(req.StreamURL), "srt")
return isRTMP || isSRT
}
// StreamInfo connects to the SRT stream to discovery media properties.
func (c *LibAVFFmpeg) StreamInfo(req entities.DonutAppetizer) (*entities.StreamInfo, error) {
closer := astikit.NewCloser()
defer closer.Close()
var inputFormatContext *astiav.FormatContext
if inputFormatContext = astiav.AllocFormatContext(); inputFormatContext == nil {
return nil, entities.ErrFFmpegLibAVFormatContextIsNil
}
closer.Add(inputFormatContext.Free)
inputFormat, err := c.defineInputFormat(req.Format.String())
if err != nil {
return nil, err
}
inputOptions := c.defineInputOptions(req.Options, closer)
if err := inputFormatContext.OpenInput(req.URL, inputFormat, inputOptions); err != nil {
return nil, fmt.Errorf("error while inputFormatContext.OpenInput: (%s, %#v, %#v) %w", req.URL, inputFormat, inputOptions, err)
}
closer.Add(inputFormatContext.CloseInput)
if err := inputFormatContext.FindStreamInfo(nil); err != nil {
return nil, fmt.Errorf("error while inputFormatContext.FindStreamInfo %w", err)
}
streams := []entities.Stream{}
for _, is := range inputFormatContext.Streams() {
if is.CodecParameters().MediaType() != astiav.MediaTypeAudio &&
is.CodecParameters().MediaType() != astiav.MediaTypeVideo {
c.l.Info("skipping media type", is.CodecParameters().MediaType())
continue
}
streams = append(streams, c.m.FromLibAVStreamToEntityStream(is))
}
si := entities.StreamInfo{Streams: streams}
return &si, nil
}
// TODO: merge common behavior (streamer / prober)
func (c *LibAVFFmpeg) defineInputFormat(streamFormat string) (*astiav.InputFormat, error) {
var inputFormat *astiav.InputFormat
if streamFormat != "" {
inputFormat = astiav.FindInputFormat(streamFormat)
if inputFormat == nil {
return nil, fmt.Errorf("ffmpeg/libav: could not find %s input format", streamFormat)
}
}
return inputFormat, nil
}
func (c *LibAVFFmpeg) defineInputOptions(opts map[entities.DonutInputOptionKey]string, closer *astikit.Closer) *astiav.Dictionary {
var dic *astiav.Dictionary
if len(opts) > 0 {
dic = &astiav.Dictionary{}
closer.Add(dic.Free)
for k, v := range opts {
dic.Set(k.String(), v, 0)
}
}
return dic
}