add v2 compatible config.

This commit is contained in:
Miroslav Šedivý 2024-07-18 21:48:09 +02:00
parent f2eab73847
commit dbf9fc6ebc
10 changed files with 889 additions and 0 deletions

View File

@ -87,6 +87,27 @@ func (c *serve) Init(cmd *cobra.Command) error {
return err return err
} }
// V2 configuration
if err := c.configs.Desktop.InitV2(cmd); err != nil {
return err
}
if err := c.configs.Capture.InitV2(cmd); err != nil {
return err
}
if err := c.configs.WebRTC.InitV2(cmd); err != nil {
return err
}
if err := c.configs.Member.InitV2(cmd); err != nil {
return err
}
if err := c.configs.Session.InitV2(cmd); err != nil {
return err
}
if err := c.configs.Server.InitV2(cmd); err != nil {
return err
}
return nil return nil
} }
@ -100,6 +121,13 @@ func (c *serve) PreRun(cmd *cobra.Command, args []string) {
c.configs.Session.Set() c.configs.Session.Set()
c.configs.Plugins.Set() c.configs.Plugins.Set()
c.configs.Server.Set() c.configs.Server.Set()
c.configs.Desktop.SetV2()
c.configs.Capture.SetV2()
c.configs.WebRTC.SetV2()
c.configs.Member.SetV2()
c.configs.Session.SetV2()
c.configs.Server.SetV2()
} }
func (c *serve) Start(cmd *cobra.Command) { func (c *serve) Start(cmd *cobra.Command) {

View File

@ -2,7 +2,9 @@ package config
import ( import (
"os" "os"
"strings"
"github.com/pion/webrtc/v3"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -12,6 +14,17 @@ import (
"github.com/demodesk/neko/pkg/utils" "github.com/demodesk/neko/pkg/utils"
) )
// Legacy capture configuration
type HwEnc int
// Legacy capture configuration
const (
HwEncUnset HwEnc = iota
HwEncNone
HwEncVAAPI
HwEncNVENC
)
type Capture struct { type Capture struct {
Display string Display string
@ -167,6 +180,132 @@ func (Capture) Init(cmd *cobra.Command) error {
return nil return nil
} }
func (Capture) InitV2(cmd *cobra.Command) error {
cmd.PersistentFlags().String("display", "", "XDisplay to capture")
if err := viper.BindPFlag("display", cmd.PersistentFlags().Lookup("display")); err != nil {
return err
}
cmd.PersistentFlags().String("video_codec", "", "video codec to be used")
if err := viper.BindPFlag("video_codec", cmd.PersistentFlags().Lookup("video_codec")); err != nil {
return err
}
// DEPRECATED: video codec
cmd.PersistentFlags().Bool("vp8", false, "DEPRECATED: use video_codec")
if err := viper.BindPFlag("vp8", cmd.PersistentFlags().Lookup("vp8")); err != nil {
return err
}
// DEPRECATED: video codec
cmd.PersistentFlags().Bool("vp9", false, "DEPRECATED: use video_codec")
if err := viper.BindPFlag("vp9", cmd.PersistentFlags().Lookup("vp9")); err != nil {
return err
}
// DEPRECATED: video codec
cmd.PersistentFlags().Bool("av1", false, "DEPRECATED: use video_codec")
if err := viper.BindPFlag("av1", cmd.PersistentFlags().Lookup("av1")); err != nil {
return err
}
// DEPRECATED: video codec
cmd.PersistentFlags().Bool("h264", false, "DEPRECATED: use video_codec")
if err := viper.BindPFlag("h264", cmd.PersistentFlags().Lookup("h264")); err != nil {
return err
}
cmd.PersistentFlags().String("hwenc", "", "use hardware accelerated encoding")
if err := viper.BindPFlag("hwenc", cmd.PersistentFlags().Lookup("hwenc")); err != nil {
return err
}
cmd.PersistentFlags().Int("video_bitrate", 0, "video bitrate in kbit/s")
if err := viper.BindPFlag("video_bitrate", cmd.PersistentFlags().Lookup("video_bitrate")); err != nil {
return err
}
cmd.PersistentFlags().Int("max_fps", 0, "maximum fps delivered via WebRTC, 0 is for no maximum")
if err := viper.BindPFlag("max_fps", cmd.PersistentFlags().Lookup("max_fps")); err != nil {
return err
}
cmd.PersistentFlags().String("video", "", "video codec parameters to use for streaming")
if err := viper.BindPFlag("video", cmd.PersistentFlags().Lookup("video")); err != nil {
return err
}
//
// audio
//
cmd.PersistentFlags().String("device", "", "audio device to capture")
if err := viper.BindPFlag("device", cmd.PersistentFlags().Lookup("device")); err != nil {
return err
}
cmd.PersistentFlags().String("audio_codec", "", "audio codec to be used")
if err := viper.BindPFlag("audio_codec", cmd.PersistentFlags().Lookup("audio_codec")); err != nil {
return err
}
// DEPRECATED: audio codec
cmd.PersistentFlags().Bool("opus", false, "DEPRECATED: use audio_codec")
if err := viper.BindPFlag("opus", cmd.PersistentFlags().Lookup("opus")); err != nil {
return err
}
// DEPRECATED: audio codec
cmd.PersistentFlags().Bool("g722", false, "DEPRECATED: use audio_codec")
if err := viper.BindPFlag("g722", cmd.PersistentFlags().Lookup("g722")); err != nil {
return err
}
// DEPRECATED: audio codec
cmd.PersistentFlags().Bool("pcmu", false, "DEPRECATED: use audio_codec")
if err := viper.BindPFlag("pcmu", cmd.PersistentFlags().Lookup("pcmu")); err != nil {
return err
}
// DEPRECATED: audio codec
cmd.PersistentFlags().Bool("pcma", false, "DEPRECATED: use audio_codec")
if err := viper.BindPFlag("pcma", cmd.PersistentFlags().Lookup("pcma")); err != nil {
return err
}
// audio codecs
cmd.PersistentFlags().Int("audio_bitrate", 0, "audio bitrate in kbit/s")
if err := viper.BindPFlag("audio_bitrate", cmd.PersistentFlags().Lookup("audio_bitrate")); err != nil {
return err
}
cmd.PersistentFlags().String("audio", "", "audio codec parameters to use for streaming")
if err := viper.BindPFlag("audio", cmd.PersistentFlags().Lookup("audio")); err != nil {
return err
}
//
// broadcast
//
cmd.PersistentFlags().String("broadcast_pipeline", "", "custom gst pipeline used for broadcasting, strings {url} {device} {display} will be replaced")
if err := viper.BindPFlag("broadcast_pipeline", cmd.PersistentFlags().Lookup("broadcast_pipeline")); err != nil {
return err
}
cmd.PersistentFlags().String("broadcast_url", "", "a default default URL for broadcast streams, can be disabled/changed later by admins in the GUI")
if err := viper.BindPFlag("broadcast_url", cmd.PersistentFlags().Lookup("broadcast_url")); err != nil {
return err
}
cmd.PersistentFlags().Bool("broadcast_autostart", false, "automatically start broadcasting when neko starts and broadcast_url is set")
if err := viper.BindPFlag("broadcast_autostart", cmd.PersistentFlags().Lookup("broadcast_autostart")); err != nil {
return err
}
return nil
}
func (s *Capture) Set() { func (s *Capture) Set() {
var ok bool var ok bool
@ -251,3 +390,135 @@ func (s *Capture) Set() {
s.MicrophoneEnabled = viper.GetBool("capture.microphone.enabled") s.MicrophoneEnabled = viper.GetBool("capture.microphone.enabled")
s.MicrophoneDevice = viper.GetString("capture.microphone.device") s.MicrophoneDevice = viper.GetString("capture.microphone.device")
} }
func (s *Capture) SetV2() {
var ok bool
//
// video
//
if display := viper.GetString("display"); display != "" {
s.Display = display
log.Warn().Msg("you are using v2 configuration 'NEKO_DISPLAY' which is deprecated, please use 'DISPLAY' instead")
}
if videoCodec := viper.GetString("video_codec"); videoCodec != "" {
s.VideoCodec, ok = codec.ParseStr(videoCodec)
if !ok || s.VideoCodec.Type != webrtc.RTPCodecTypeVideo {
log.Warn().Str("codec", videoCodec).Msgf("unknown video codec, using Vp8")
s.VideoCodec = codec.VP8()
}
log.Warn().Msg("you are using v2 configuration 'NEKO_VIDEO_CODEC' which is deprecated, please use 'NEKO_CAPTURE_VIDEO_CODEC' instead")
}
if viper.GetBool("vp8") {
s.VideoCodec = codec.VP8()
log.Warn().Msg("you are using deprecated config setting 'NEKO_VP8=true', use 'NEKO_CAPTURE_VIDEO_CODEC=vp8' instead")
} else if viper.GetBool("vp9") {
s.VideoCodec = codec.VP9()
log.Warn().Msg("you are using deprecated config setting 'NEKO_VP9=true', use 'NEKO_CAPTURE_VIDEO_CODEC=vp9' instead")
} else if viper.GetBool("h264") {
s.VideoCodec = codec.H264()
log.Warn().Msg("you are using deprecated config setting 'NEKO_H264=true', use 'NEKO_CAPTURE_VIDEO_CODEC=h264' instead")
} else if viper.GetBool("av1") {
s.VideoCodec = codec.AV1()
log.Warn().Msg("you are using deprecated config setting 'NEKO_AV1=true', use 'NEKO_CAPTURE_VIDEO_CODEC=av1' instead")
}
videoHWEnc := HwEncUnset
if hwenc := strings.ToLower(viper.GetString("hwenc")); hwenc != "" {
switch hwenc {
case "none":
videoHWEnc = HwEncNone
case "vaapi":
videoHWEnc = HwEncVAAPI
case "nvenc":
videoHWEnc = HwEncNVENC
default:
log.Warn().Str("hwenc", hwenc).Msgf("unknown video hw encoder, using CPU")
}
}
videoBitrate := viper.GetUint("video_bitrate")
videoMaxFPS := int16(viper.GetInt("max_fps"))
videoPipeline := viper.GetString("video")
// video pipeline
if videoHWEnc != HwEncUnset || videoBitrate != 0 || videoMaxFPS != 0 || videoPipeline != "" {
pipeline, err := NewVideoPipeline(s.VideoCodec, s.Display, videoPipeline, videoMaxFPS, videoBitrate, videoHWEnc)
if err != nil {
log.Warn().Err(err).Msg("unable to create video pipeline, using default")
} else {
s.VideoPipelines = map[string]types.VideoConfig{
"main": {
GstPipeline: pipeline,
},
}
// TODO: add deprecated warning and proper alternative
}
}
//
// audio
//
if audioDevice := viper.GetString("device"); audioDevice != "" {
s.AudioDevice = audioDevice
log.Warn().Msg("you are using v2 configuration 'NEKO_DEVICE' which is deprecated, please use 'NEKO_CAPTURE_AUDIO_DEVICE' instead")
}
if audioCodec := viper.GetString("audio_codec"); audioCodec != "" {
s.AudioCodec, ok = codec.ParseStr(audioCodec)
if !ok || s.AudioCodec.Type != webrtc.RTPCodecTypeAudio {
log.Warn().Str("codec", audioCodec).Msgf("unknown audio codec, using Opus")
s.AudioCodec = codec.Opus()
}
log.Warn().Msg("you are using v2 configuration 'NEKO_AUDIO_CODEC' which is deprecated, please use 'NEKO_CAPTURE_AUDIO_CODEC' instead")
}
if viper.GetBool("opus") {
s.AudioCodec = codec.Opus()
log.Warn().Msg("you are using deprecated config setting 'NEKO_OPUS=true', use 'NEKO_CAPTURE_AUDIO_CODEC=opus' instead")
} else if viper.GetBool("g722") {
s.AudioCodec = codec.G722()
log.Warn().Msg("you are using deprecated config setting 'NEKO_G722=true', use 'NEKO_CAPTURE_AUDIO_CODEC=g722' instead")
} else if viper.GetBool("pcmu") {
s.AudioCodec = codec.PCMU()
log.Warn().Msg("you are using deprecated config setting 'NEKO_PCMU=true', use 'NEKO_CAPTURE_AUDIO_CODEC=pcmu' instead")
} else if viper.GetBool("pcma") {
s.AudioCodec = codec.PCMA()
log.Warn().Msg("you are using deprecated config setting 'NEKO_PCMA=true', use 'NEKO_CAPTURE_AUDIO_CODEC=pcma' instead")
}
audioBitrate := viper.GetUint("audio_bitrate")
audioPipeline := viper.GetString("audio")
// audio pipeline
if audioBitrate != 0 || audioPipeline != "" {
pipeline, err := NewAudioPipeline(s.AudioCodec, s.AudioDevice, audioPipeline, audioBitrate)
if err != nil {
log.Warn().Err(err).Msg("unable to create audio pipeline, using default")
} else {
s.AudioPipeline = pipeline
}
// TODO: add deprecated warning and proper alternative
}
//
// broadcast
//
if viper.IsSet("broadcast_pipeline") {
s.BroadcastPipeline = viper.GetString("broadcast_pipeline")
log.Warn().Msg("you are using v2 configuration 'NEKO_BROADCAST_PIPELINE' which is deprecated, please use 'NEKO_CAPTURE_BROADCAST_PIPELINE' instead")
}
if viper.IsSet("broadcast_url") {
s.BroadcastUrl = viper.GetString("broadcast_url")
log.Warn().Msg("you are using v2 configuration 'NEKO_BROADCAST_URL' which is deprecated, please use 'NEKO_CAPTURE_BROADCAST_URL' instead")
}
if viper.IsSet("broadcast_autostart") {
s.BroadcastAutostart = viper.GetBool("broadcast_autostart")
log.Warn().Msg("you are using v2 configuration 'NEKO_BROADCAST_AUTOSTART' which is deprecated, please use 'NEKO_CAPTURE_BROADCAST_AUTOSTART' instead")
}
}

View File

@ -0,0 +1,235 @@
// Legacy pipeline configuration for gstreamer.
package config
import (
"fmt"
"strings"
"github.com/demodesk/neko/pkg/gst"
"github.com/demodesk/neko/pkg/types/codec"
)
/*
apt-get install \
libgstreamer1.0-0 \
gstreamer1.0-plugins-base \
gstreamer1.0-plugins-good \
gstreamer1.0-plugins-bad \
gstreamer1.0-plugins-ugly\
gstreamer1.0-libav \
gstreamer1.0-doc \
gstreamer1.0-tools \
gstreamer1.0-x \
gstreamer1.0-alsa \
gstreamer1.0-pulseaudio
gst-inspect-1.0 --version
gst-inspect-1.0 plugin
gst-launch-1.0 ximagesrc show-pointer=true use-damage=false ! video/x-raw,framerate=30/1 ! videoconvert ! queue ! vp8enc error-resilient=partitions keyframe-max-dist=10 auto-alt-ref=true cpu-used=5 deadline=1 ! autovideosink
gst-launch-1.0 pulsesrc ! audioconvert ! opusenc ! autoaudiosink
*/
const (
videoSrc = "ximagesrc display-name=%s show-pointer=true use-damage=false ! video/x-raw,framerate=%d/1 ! videoconvert ! queue ! "
audioSrc = "pulsesrc device=%s ! audio/x-raw,channels=2 ! audioconvert ! "
)
func NewBroadcastPipeline(device string, display string, pipelineSrc string, url string) string {
video := fmt.Sprintf(videoSrc, display, 25)
audio := fmt.Sprintf(audioSrc, device)
var pipelineStr string
if pipelineSrc != "" {
// replace RTMP url
pipelineStr = strings.Replace(pipelineSrc, "{url}", url, -1)
// replace audio device
pipelineStr = strings.Replace(pipelineStr, "{device}", device, -1)
// replace display
pipelineStr = strings.Replace(pipelineStr, "{display}", display, -1)
} else {
pipelineStr = fmt.Sprintf("flvmux name=mux ! rtmpsink location='%s live=1' %s audio/x-raw,channels=2 ! audioconvert ! voaacenc ! mux. %s x264enc bframes=0 key-int-max=60 byte-stream=true tune=zerolatency speed-preset=veryfast ! mux.", url, audio, video)
}
return pipelineStr
}
func NewVideoPipeline(rtpCodec codec.RTPCodec, display string, pipelineSrc string, fps int16, bitrate uint, hwenc HwEnc) (string, error) {
pipelineStr := " ! appsink name=appsinkvideo"
// if using custom pipeline
if pipelineSrc != "" {
pipelineStr = fmt.Sprintf(pipelineSrc+pipelineStr, display)
return pipelineStr, nil
}
// use default fps if not set
if fps == 0 {
fps = 25
}
switch rtpCodec.Name {
case codec.VP8().Name:
if hwenc == HwEncVAAPI {
if err := gst.CheckPlugins([]string{"ximagesrc", "vaapi"}); err != nil {
return "", err
}
// vp8 encode is missing from gstreamer.freedesktop.org/documentation
// note that it was removed from some recent intel CPUs: https://trac.ffmpeg.org/wiki/Hardware/QuickSync
// https://gstreamer.freedesktop.org/data/doc/gstreamer/head/gstreamer-vaapi-plugins/html/gstreamer-vaapi-plugins-vaapivp8enc.html
pipelineStr = fmt.Sprintf(videoSrc+"video/x-raw,format=NV12 ! vaapivp8enc rate-control=vbr bitrate=%d keyframe-period=180"+pipelineStr, display, fps, bitrate)
} else {
// https://gstreamer.freedesktop.org/documentation/vpx/vp8enc.html?gi-language=c
// gstreamer1.0-plugins-good
// vp8enc error-resilient=partitions keyframe-max-dist=10 auto-alt-ref=true cpu-used=5 deadline=1
if err := gst.CheckPlugins([]string{"ximagesrc", "vpx"}); err != nil {
return "", err
}
pipelineStr = strings.Join([]string{
fmt.Sprintf(videoSrc, display, fps),
"vp8enc",
fmt.Sprintf("target-bitrate=%d", bitrate*650),
"cpu-used=4",
"end-usage=cbr",
"threads=4",
"deadline=1",
"undershoot=95",
fmt.Sprintf("buffer-size=%d", bitrate*4),
fmt.Sprintf("buffer-initial-size=%d", bitrate*2),
fmt.Sprintf("buffer-optimal-size=%d", bitrate*3),
"keyframe-max-dist=25",
"min-quantizer=4",
"max-quantizer=20",
pipelineStr,
}, " ")
}
case codec.VP9().Name:
// https://gstreamer.freedesktop.org/documentation/vpx/vp9enc.html?gi-language=c
// gstreamer1.0-plugins-good
// vp9enc
if err := gst.CheckPlugins([]string{"ximagesrc", "vpx"}); err != nil {
return "", err
}
pipelineStr = fmt.Sprintf(videoSrc+"vp9enc target-bitrate=%d cpu-used=-5 threads=4 deadline=1 keyframe-max-dist=30 auto-alt-ref=true"+pipelineStr, display, fps, bitrate*1000)
case codec.AV1().Name:
// https://gstreamer.freedesktop.org/documentation/aom/av1enc.html?gi-language=c
// gstreamer1.0-plugins-bad
// av1enc usage-profile=1
// TODO: check for plugin.
if err := gst.CheckPlugins([]string{"ximagesrc", "vpx"}); err != nil {
return "", err
}
pipelineStr = strings.Join([]string{
fmt.Sprintf(videoSrc, display, fps),
"av1enc",
fmt.Sprintf("target-bitrate=%d", bitrate*650),
"cpu-used=4",
"end-usage=cbr",
// "usage-profile=realtime",
"undershoot=95",
"keyframe-max-dist=25",
"min-quantizer=4",
"max-quantizer=20",
pipelineStr,
}, " ")
case codec.H264().Name:
if err := gst.CheckPlugins([]string{"ximagesrc"}); err != nil {
return "", err
}
vbvbuf := uint(1000)
if bitrate > 1000 {
vbvbuf = bitrate
}
if hwenc == HwEncVAAPI {
if err := gst.CheckPlugins([]string{"vaapi"}); err != nil {
return "", err
}
pipelineStr = fmt.Sprintf(videoSrc+"video/x-raw,format=NV12 ! vaapih264enc rate-control=vbr bitrate=%d keyframe-period=180 quality-level=7 ! video/x-h264,stream-format=byte-stream,profile=constrained-baseline"+pipelineStr, display, fps, bitrate)
} else if hwenc == HwEncNVENC {
if err := gst.CheckPlugins([]string{"nvcodec"}); err != nil {
return "", err
}
pipelineStr = fmt.Sprintf(videoSrc+"video/x-raw,format=NV12 ! nvh264enc name=encoder preset=2 gop-size=25 spatial-aq=true temporal-aq=true bitrate=%d vbv-buffer-size=%d rc-mode=6 ! h264parse config-interval=-1 ! video/x-h264,stream-format=byte-stream,profile=constrained-baseline"+pipelineStr, display, fps, bitrate, vbvbuf)
} else {
// https://gstreamer.freedesktop.org/documentation/openh264/openh264enc.html?gi-language=c#openh264enc
// gstreamer1.0-plugins-bad
// openh264enc multi-thread=4 complexity=high bitrate=3072000 max-bitrate=4096000
if err := gst.CheckPlugins([]string{"openh264"}); err == nil {
pipelineStr = fmt.Sprintf(videoSrc+"openh264enc multi-thread=4 complexity=high bitrate=%d max-bitrate=%d ! video/x-h264,stream-format=byte-stream,profile=constrained-baseline"+pipelineStr, display, fps, bitrate*1000, (bitrate+1024)*1000)
break
}
// https://gstreamer.freedesktop.org/documentation/x264/index.html?gi-language=c
// gstreamer1.0-plugins-ugly
// video/x-raw,format=I420 ! x264enc bframes=0 key-int-max=60 byte-stream=true tune=zerolatency speed-preset=veryfast ! video/x-h264,stream-format=byte-stream,profile=constrained-baseline
if err := gst.CheckPlugins([]string{"x264"}); err != nil {
return "", err
}
pipelineStr = fmt.Sprintf(videoSrc+"video/x-raw,format=NV12 ! x264enc threads=4 bitrate=%d key-int-max=60 vbv-buf-capacity=%d byte-stream=true tune=zerolatency speed-preset=veryfast ! video/x-h264,stream-format=byte-stream,profile=constrained-baseline"+pipelineStr, display, fps, bitrate, vbvbuf)
}
default:
return "", fmt.Errorf("unknown codec %s", rtpCodec.Name)
}
return pipelineStr, nil
}
func NewAudioPipeline(rtpCodec codec.RTPCodec, device string, pipelineSrc string, bitrate uint) (string, error) {
pipelineStr := " ! appsink name=appsinkaudio"
// if using custom pipeline
if pipelineSrc != "" {
pipelineStr = fmt.Sprintf(pipelineSrc+pipelineStr, device)
return pipelineStr, nil
}
switch rtpCodec.Name {
case codec.Opus().Name:
// https://gstreamer.freedesktop.org/documentation/opus/opusenc.html
// gstreamer1.0-plugins-base
// opusenc
if err := gst.CheckPlugins([]string{"pulseaudio", "opus"}); err != nil {
return "", err
}
pipelineStr = fmt.Sprintf(audioSrc+"opusenc inband-fec=true bitrate=%d"+pipelineStr, device, bitrate*1000)
case codec.G722().Name:
// https://gstreamer.freedesktop.org/documentation/libav/avenc_g722.html?gi-language=c
// gstreamer1.0-libav
// avenc_g722
if err := gst.CheckPlugins([]string{"pulseaudio", "libav"}); err != nil {
return "", err
}
pipelineStr = fmt.Sprintf(audioSrc+"avenc_g722 bitrate=%d"+pipelineStr, device, bitrate*1000)
case codec.PCMU().Name:
// https://gstreamer.freedesktop.org/documentation/mulaw/mulawenc.html?gi-language=c
// gstreamer1.0-plugins-good
// audio/x-raw, rate=8000 ! mulawenc
if err := gst.CheckPlugins([]string{"pulseaudio", "mulaw"}); err != nil {
return "", err
}
pipelineStr = fmt.Sprintf(audioSrc+"audio/x-raw, rate=8000 ! mulawenc"+pipelineStr, device)
case codec.PCMA().Name:
// https://gstreamer.freedesktop.org/documentation/alaw/alawenc.html?gi-language=c
// gstreamer1.0-plugins-good
// audio/x-raw, rate=8000 ! alawenc
if err := gst.CheckPlugins([]string{"pulseaudio", "alaw"}); err != nil {
return "", err
}
pipelineStr = fmt.Sprintf(audioSrc+"audio/x-raw, rate=8000 ! alawenc"+pipelineStr, device)
default:
return "", fmt.Errorf("unknown codec %s", rtpCodec.Name)
}
return pipelineStr, nil
}

View File

@ -5,6 +5,7 @@ import (
"regexp" "regexp"
"strconv" "strconv"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -58,6 +59,15 @@ func (Desktop) Init(cmd *cobra.Command) error {
return nil return nil
} }
func (Desktop) InitV2(cmd *cobra.Command) error {
cmd.PersistentFlags().String("screen", "", "default screen resolution and framerate")
if err := viper.BindPFlag("screen", cmd.PersistentFlags().Lookup("screen")); err != nil {
return err
}
return nil
}
func (s *Desktop) Set() { func (s *Desktop) Set() {
// Display is provided by env variable // Display is provided by env variable
s.Display = os.Getenv("DISPLAY") s.Display = os.Getenv("DISPLAY")
@ -89,3 +99,23 @@ func (s *Desktop) Set() {
s.UploadDrop = viper.GetBool("desktop.upload_drop") s.UploadDrop = viper.GetBool("desktop.upload_drop")
s.FileChooserDialog = viper.GetBool("desktop.file_chooser_dialog") s.FileChooserDialog = viper.GetBool("desktop.file_chooser_dialog")
} }
func (s *Desktop) SetV2() {
if viper.IsSet("screen") {
r := regexp.MustCompile(`([0-9]{1,4})x([0-9]{1,4})@([0-9]{1,3})`)
res := r.FindStringSubmatch(viper.GetString("screen"))
if len(res) > 0 {
width, err1 := strconv.ParseInt(res[1], 10, 64)
height, err2 := strconv.ParseInt(res[2], 10, 64)
rate, err3 := strconv.ParseInt(res[3], 10, 64)
if err1 == nil && err2 == nil && err3 == nil {
s.ScreenSize.Width = int(width)
s.ScreenSize.Height = int(height)
s.ScreenSize.Rate = int16(rate)
}
}
log.Warn().Msg("you are using v2 configuration 'NEKO_SCREEN' which is deprecated, please use 'NEKO_DESKTOP_SCREEN' instead")
}
}

View File

@ -68,6 +68,20 @@ func (Member) Init(cmd *cobra.Command) error {
return nil return nil
} }
func (Member) InitV2(cmd *cobra.Command) error {
cmd.PersistentFlags().String("password", "", "password for connecting to stream")
if err := viper.BindPFlag("password", cmd.PersistentFlags().Lookup("password")); err != nil {
return err
}
cmd.PersistentFlags().String("password_admin", "", "admin password for connecting to stream")
if err := viper.BindPFlag("password_admin", cmd.PersistentFlags().Lookup("password_admin")); err != nil {
return err
}
return nil
}
func (s *Member) Set() { func (s *Member) Set() {
s.Provider = viper.GetString("member.provider") s.Provider = viper.GetString("member.provider")
@ -126,3 +140,20 @@ func (s *Member) Set() {
log.Warn().Err(err).Msgf("unable to parse member multiuser admin profile") log.Warn().Err(err).Msgf("unable to parse member multiuser admin profile")
} }
} }
func (s *Member) SetV2() {
if viper.IsSet("password") || viper.IsSet("password_admin") {
s.Provider = "multiuser"
if userPassword := viper.GetString("password"); userPassword != "" {
s.Multiuser.UserPassword = userPassword
} else {
s.Multiuser.UserPassword = "neko"
}
if adminPassword := viper.GetString("password_admin"); adminPassword != "" {
s.Multiuser.AdminPassword = adminPassword
} else {
s.Multiuser.AdminPassword = "admin"
}
log.Warn().Msg("you are using v2 configuration 'NEKO_PASSWORD' and 'NEKO_PASSWORD_ADMIN' which are deprecated, please use 'NEKO_MEMBER_MULTIUSER_USER_PASSWORD' and 'NEKO_MEMBER_MULTIUSER_ADMIN_PASSWORD' with 'NEKO_MEMBER_PROVIDER=multiuser' instead")
}
}

View File

@ -2,6 +2,8 @@ package config
import ( import (
"os" "os"
"path/filepath"
"runtime"
"github.com/rs/zerolog" "github.com/rs/zerolog"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
@ -59,6 +61,15 @@ func (Root) Init(cmd *cobra.Command) error {
return nil return nil
} }
func (Root) InitV2(cmd *cobra.Command) error {
cmd.PersistentFlags().BoolP("logs", "l", false, "save logs to file")
if err := viper.BindPFlag("logs", cmd.PersistentFlags().Lookup("logs")); err != nil {
return err
}
return nil
}
func (s *Root) Set() { func (s *Root) Set() {
s.Config = viper.GetString("config") s.Config = viper.GetString("config")
@ -95,3 +106,18 @@ func (s *Root) Set() {
s.LogNocolor = true s.LogNocolor = true
} }
} }
func (s *Root) SetV2() {
if viper.IsSet("logs") {
if viper.GetBool("logs") {
logs := filepath.Join(".", "logs")
if runtime.GOOS == "linux" {
logs = "/var/log/neko"
}
s.LogDir = logs
} else {
s.LogDir = ""
}
log.Warn().Msg("you are using v2 configuration 'NEKO_LOGS' which is deprecated, please use 'NEKO_LOG_DIR=/path/to/logs' instead")
}
}

View File

@ -3,6 +3,7 @@ package config
import ( import (
"path" "path"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -70,6 +71,45 @@ func (Server) Init(cmd *cobra.Command) error {
return nil return nil
} }
func (Server) InitV2(cmd *cobra.Command) error {
cmd.PersistentFlags().String("bind", "", "address/port/socket to serve neko")
if err := viper.BindPFlag("bind", cmd.PersistentFlags().Lookup("bind")); err != nil {
return err
}
cmd.PersistentFlags().String("cert", "", "path to the SSL cert used to secure the neko server")
if err := viper.BindPFlag("cert", cmd.PersistentFlags().Lookup("cert")); err != nil {
return err
}
cmd.PersistentFlags().String("key", "", "path to the SSL key used to secure the neko server")
if err := viper.BindPFlag("key", cmd.PersistentFlags().Lookup("key")); err != nil {
return err
}
cmd.PersistentFlags().Bool("proxy", false, "enable reverse proxy mode")
if err := viper.BindPFlag("proxy", cmd.PersistentFlags().Lookup("proxy")); err != nil {
return err
}
cmd.PersistentFlags().String("static", "", "path to neko client files to serve")
if err := viper.BindPFlag("static", cmd.PersistentFlags().Lookup("static")); err != nil {
return err
}
cmd.PersistentFlags().String("path_prefix", "", "path prefix for HTTP requests")
if err := viper.BindPFlag("path_prefix", cmd.PersistentFlags().Lookup("path_prefix")); err != nil {
return err
}
cmd.PersistentFlags().StringSlice("cors", []string{}, "list of allowed origins for CORS")
if err := viper.BindPFlag("cors", cmd.PersistentFlags().Lookup("cors")); err != nil {
return err
}
return nil
}
func (s *Server) Set() { func (s *Server) Set() {
s.Cert = viper.GetString("server.cert") s.Cert = viper.GetString("server.cert")
s.Key = viper.GetString("server.key") s.Key = viper.GetString("server.key")
@ -87,6 +127,41 @@ func (s *Server) Set() {
} }
} }
func (s *Server) SetV2() {
if viper.IsSet("cert") {
s.Cert = viper.GetString("cert")
log.Warn().Msg("you are using v2 configuration 'NEKO_CERT' which is deprecated, please use 'NEKO_SERVER_CERT' instead")
}
if viper.IsSet("key") {
s.Key = viper.GetString("key")
log.Warn().Msg("you are using v2 configuration 'NEKO_KEY' which is deprecated, please use 'NEKO_SERVER_KEY' instead")
}
if viper.IsSet("bind") {
s.Bind = viper.GetString("bind")
log.Warn().Msg("you are using v2 configuration 'NEKO_BIND' which is deprecated, please use 'NEKO_SERVER_BIND' instead")
}
if viper.IsSet("proxy") {
s.Proxy = viper.GetBool("proxy")
log.Warn().Msg("you are using v2 configuration 'NEKO_PROXY' which is deprecated, please use 'NEKO_SERVER_PROXY' instead")
}
if viper.IsSet("static") {
s.Static = viper.GetString("static")
log.Warn().Msg("you are using v2 configuration 'NEKO_STATIC' which is deprecated, please use 'NEKO_SERVER_STATIC' instead")
}
if viper.IsSet("path_prefix") {
s.PathPrefix = path.Join("/", path.Clean(viper.GetString("path_prefix")))
log.Warn().Msg("you are using v2 configuration 'NEKO_PATH_PREFIX' which is deprecated, please use 'NEKO_SERVER_PATH_PREFIX' instead")
}
if viper.IsSet("cors") {
s.CORS = viper.GetStringSlice("cors")
in, _ := utils.ArrayIn("*", s.CORS)
if len(s.CORS) == 0 || in {
s.CORS = []string{"*"}
}
log.Warn().Msg("you are using v2 configuration 'NEKO_CORS' which is deprecated, please use 'NEKO_SERVER_CORS' instead")
}
}
func (s *Server) HasCors() bool { func (s *Server) HasCors() bool {
return len(s.CORS) > 0 return len(s.CORS) > 0
} }

View File

@ -3,6 +3,7 @@ package config
import ( import (
"time" "time"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
@ -95,6 +96,25 @@ func (Session) Init(cmd *cobra.Command) error {
return nil return nil
} }
func (Session) InitV2(cmd *cobra.Command) error {
cmd.PersistentFlags().StringSlice("locks", []string{}, "resources, that will be locked when starting (control, login)")
if err := viper.BindPFlag("locks", cmd.PersistentFlags().Lookup("locks")); err != nil {
return err
}
cmd.PersistentFlags().Bool("control_protection", false, "control protection means, users can gain control only if at least one admin is in the room")
if err := viper.BindPFlag("control_protection", cmd.PersistentFlags().Lookup("control_protection")); err != nil {
return err
}
cmd.PersistentFlags().Bool("implicit_control", false, "if enabled members can gain control implicitly")
if err := viper.BindPFlag("implicit_control", cmd.PersistentFlags().Lookup("implicit_control")); err != nil {
return err
}
return nil
}
func (s *Session) Set() { func (s *Session) Set() {
s.File = viper.GetString("session.file") s.File = viper.GetString("session.file")
@ -112,3 +132,28 @@ func (s *Session) Set() {
s.CookieExpiration = time.Duration(viper.GetInt("session.cookie.expiration")) * time.Hour s.CookieExpiration = time.Duration(viper.GetInt("session.cookie.expiration")) * time.Hour
s.CookieSecure = viper.GetBool("session.cookie.secure") s.CookieSecure = viper.GetBool("session.cookie.secure")
} }
func (s *Session) SetV2() {
if viper.IsSet("locks") {
locks := viper.GetStringSlice("locks")
for _, lock := range locks {
switch lock {
// TODO: file_transfer
case "control":
s.LockedControls = true
case "login":
s.LockedLogins = true
}
}
log.Warn().Msg("you are using v2 configuration 'NEKO_LOCKS' which is deprecated, please use 'NEKO_SESSION_LOCKED_CONTROLS' and 'NEKO_SESSION_LOCKED_LOGINS' instead")
}
if viper.IsSet("implicit_control") {
s.ImplicitHosting = viper.GetBool("implicit_control")
log.Warn().Msg("you are using v2 configuration 'NEKO_IMPLICIT_CONTROL' which is deprecated, please use 'NEKO_SESSION_IMPLICIT_HOSTING' instead")
}
if viper.IsSet("control_protection") {
s.ControlProtection = viper.GetBool("control_protection")
log.Warn().Msg("you are using v2 configuration 'NEKO_CONTROL_PROTECTION' which is deprecated, please use 'NEKO_SESSION_CONTROL_PROTECTION' instead")
}
}

View File

@ -1,6 +1,7 @@
package config package config
import ( import (
"encoding/json"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -166,6 +167,50 @@ func (WebRTC) Init(cmd *cobra.Command) error {
return nil return nil
} }
func (WebRTC) InitV2(cmd *cobra.Command) error {
cmd.PersistentFlags().String("epr", "", "limits the pool of ephemeral ports that ICE UDP connections can allocate from")
if err := viper.BindPFlag("epr", cmd.PersistentFlags().Lookup("epr")); err != nil {
return err
}
cmd.PersistentFlags().StringSlice("nat1to1", []string{}, "sets a list of external IP addresses of 1:1 (D)NAT and a candidate type for which the external IP address is used")
if err := viper.BindPFlag("nat1to1", cmd.PersistentFlags().Lookup("nat1to1")); err != nil {
return err
}
cmd.PersistentFlags().Int("tcpmux", 0, "single TCP mux port for all peers")
if err := viper.BindPFlag("tcpmux", cmd.PersistentFlags().Lookup("tcpmux")); err != nil {
return err
}
cmd.PersistentFlags().Int("udpmux", 0, "single UDP mux port for all peers")
if err := viper.BindPFlag("udpmux", cmd.PersistentFlags().Lookup("udpmux")); err != nil {
return err
}
cmd.PersistentFlags().String("ipfetch", "", "automatically fetch IP address from given URL when nat1to1 is not present")
if err := viper.BindPFlag("ipfetch", cmd.PersistentFlags().Lookup("ipfetch")); err != nil {
return err
}
cmd.PersistentFlags().Bool("icelite", false, "configures whether or not the ice agent should be a lite agent")
if err := viper.BindPFlag("icelite", cmd.PersistentFlags().Lookup("icelite")); err != nil {
return err
}
cmd.PersistentFlags().StringSlice("iceserver", []string{}, "describes a single STUN and TURN server that can be used by the ICEAgent to establish a connection with a peer")
if err := viper.BindPFlag("iceserver", cmd.PersistentFlags().Lookup("iceserver")); err != nil {
return err
}
cmd.PersistentFlags().String("iceservers", "", "describes a single STUN and TURN server that can be used by the ICEAgent to establish a connection with a peer")
if err := viper.BindPFlag("iceservers", cmd.PersistentFlags().Lookup("iceservers")); err != nil {
return err
}
return nil
}
func (s *WebRTC) Set() { func (s *WebRTC) Set() {
s.ICELite = viper.GetBool("webrtc.icelite") s.ICELite = viper.GetBool("webrtc.icelite")
s.ICETrickle = viper.GetBool("webrtc.icetrickle") s.ICETrickle = viper.GetBool("webrtc.icetrickle")
@ -271,3 +316,84 @@ func (s *WebRTC) Set() {
s.Estimator.UpgradeBackoff = viper.GetDuration("webrtc.estimator.upgrade_backoff") s.Estimator.UpgradeBackoff = viper.GetDuration("webrtc.estimator.upgrade_backoff")
s.Estimator.DiffThreshold = viper.GetFloat64("webrtc.estimator.diff_threshold") s.Estimator.DiffThreshold = viper.GetFloat64("webrtc.estimator.diff_threshold")
} }
func (s *WebRTC) SetV2() {
if viper.IsSet("nat1to1") {
s.NAT1To1IPs = viper.GetStringSlice("nat1to1")
log.Warn().Msg("you are using v2 configuration 'NEKO_NAT1TO1' which is deprecated, please use 'NEKO_WEBRTC_NAT1TO1' instead")
}
if viper.IsSet("tcpmux") {
s.TCPMux = viper.GetInt("tcpmux")
log.Warn().Msg("you are using v2 configuration 'NEKO_TCPMUX' which is deprecated, please use 'NEKO_WEBRTC_TCPMUX' instead")
}
if viper.IsSet("udpmux") {
s.UDPMux = viper.GetInt("udpmux")
log.Warn().Msg("you are using v2 configuration 'NEKO_UDPMUX' which is deprecated, please use 'NEKO_WEBRTC_UDPMUX' instead")
}
if viper.IsSet("icelite") {
s.ICELite = viper.GetBool("icelite")
log.Warn().Msg("you are using v2 configuration 'NEKO_ICELITE' which is deprecated, please use 'NEKO_WEBRTC_ICELITE' instead")
}
if viper.IsSet("iceservers") {
iceServers := []types.ICEServer{}
iceServersJson := viper.GetString("iceservers")
if iceServersJson != "" {
err := json.Unmarshal([]byte(iceServersJson), &iceServers)
if err != nil {
log.Panic().Err(err).Msg("failed to process iceservers")
}
}
s.ICEServersFrontend = iceServers
s.ICEServersBackend = iceServers
log.Warn().Msg("you are using v2 configuration 'NEKO_ICESERVERS' which is deprecated, please use 'NEKO_WEBRTC_ICESERVERS_FRONTEND' and/or 'NEKO_WEBRTC_ICESERVERS_BACKEND' instead")
}
if viper.IsSet("iceserver") {
iceServerSlice := viper.GetStringSlice("iceserver")
if len(iceServerSlice) > 0 {
s.ICEServersFrontend = append(s.ICEServersFrontend, types.ICEServer{URLs: iceServerSlice})
s.ICEServersBackend = append(s.ICEServersBackend, types.ICEServer{URLs: iceServerSlice})
}
log.Warn().Msg("you are using v2 configuration 'NEKO_ICESERVER' which is deprecated, please use 'NEKO_WEBRTC_ICESERVERS_FRONTEND' and/or 'NEKO_WEBRTC_ICESERVERS_BACKEND' instead")
}
if viper.IsSet("ipfetch") {
if len(s.NAT1To1IPs) == 0 {
ipfetch := viper.GetString("ipfetch")
ip, err := utils.HttpRequestGET(ipfetch)
if err != nil {
log.Panic().Err(err).Str("ipfetch", ipfetch).Msg("failed to fetch ip address")
}
s.NAT1To1IPs = append(s.NAT1To1IPs, ip)
}
log.Warn().Msg("you are using v2 configuration 'NEKO_IPFETCH' which is deprecated, please use 'NEKO_WEBRTC_IP_RETRIEVAL_URL' instead")
}
if viper.IsSet("epr") {
min := uint16(59000)
max := uint16(59100)
epr := viper.GetString("epr")
ports := strings.SplitN(epr, "-", -1)
if len(ports) > 1 {
start, err := strconv.ParseUint(ports[0], 10, 16)
if err == nil {
min = uint16(start)
}
end, err := strconv.ParseUint(ports[1], 10, 16)
if err == nil {
max = uint16(end)
}
}
if min > max {
s.EphemeralMin = max
s.EphemeralMax = min
} else {
s.EphemeralMin = min
s.EphemeralMax = max
}
log.Warn().Msg("you are using v2 configuration 'NEKO_EPR' which is deprecated, please use 'NEKO_WEBRTC_EPR' instead")
}
}

View File

@ -30,6 +30,18 @@ func (Config) Init(cmd *cobra.Command) error {
return err return err
} }
// v2 config
cmd.PersistentFlags().Bool("file_transfer_enabled", false, "enable file transfer feature")
if err := viper.BindPFlag("file_transfer_enabled", cmd.PersistentFlags().Lookup("file_transfer_enabled")); err != nil {
return err
}
cmd.PersistentFlags().String("file_transfer_path", "", "path to use for file transfer")
if err := viper.BindPFlag("file_transfer_path", cmd.PersistentFlags().Lookup("file_transfer_path")); err != nil {
return err
}
return nil return nil
} }
@ -38,4 +50,14 @@ func (s *Config) Set() {
rootDir := viper.GetString("filetransfer.dir") rootDir := viper.GetString("filetransfer.dir")
s.RootDir = filepath.Clean(rootDir) s.RootDir = filepath.Clean(rootDir)
s.RefreshInterval = viper.GetDuration("filetransfer.refresh_interval") s.RefreshInterval = viper.GetDuration("filetransfer.refresh_interval")
// v2 config
if viper.IsSet("file_transfer_enabled") {
s.Enabled = viper.GetBool("file_transfer_enabled")
}
if viper.IsSet("file_transfer_path") {
rootDir = viper.GetString("file_transfer_path")
s.RootDir = filepath.Clean(rootDir)
}
} }