Skip to content

Commit

Permalink
[MM-51443] Add support for configuring recording quality settings (#26)
Browse files Browse the repository at this point in the history
* Allow full config to be passed

* Bump mm server dep

* Fix imports

* Fix tests

* Extract config package

* Support conversion from serialized data

* Forward env variables

* Bump encoding buffers
  • Loading branch information
streamer45 authored Mar 21, 2023
1 parent 130cb07 commit b29c211
Show file tree
Hide file tree
Showing 9 changed files with 305 additions and 1,965 deletions.
7 changes: 7 additions & 0 deletions build/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ runuser -l $RECORDER_USER -c \
AUTH_TOKEN=$AUTH_TOKEN \
CALL_ID=$CALL_ID \
THREAD_ID=$THREAD_ID \
WIDTH=$WIDTH \
HEIGHT=$HEIGHT \
VIDEO_RATE=$VIDEO_RATE \
AUDIO_RATE=$AUDIO_RATE \
FRAME_RATE=$FRAME_RATE \
VIDEO_PRESET=$VIDEO_PRESET \
OUTPUT_FORMAT=$OUTPUT_FORMAT \
DEV_MODE=$DEV \
XDG_RUNTIME_DIR=/home/$RECORDER_USER/.cache/xdgr \
/bin/bash -c '/opt/calls-recorder/bin/calls-recorder; echo \$? > ${RECORDER_EXIT_CODE_FILE}'" &
Expand Down
116 changes: 114 additions & 2 deletions cmd/recorder/config.go → cmd/recorder/config/config.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package config

import (
"fmt"
Expand All @@ -17,13 +17,25 @@ const (
AVFormatMP4 AVFormat = "mp4"
)

type H264Preset string

const (
H264PresetMedium = "medium"
H264PresetFast = "fast"
H264PresetFaster = "faster"
H264PresetVeryFast = "veryfast"
H264PresetSuperFast = "superfast"
H264PresetUltraFast = "ultrafast"
)

const (
// defaults
VideoWidthDefault = 1920
VideoHeightDefault = 1080
VideoRateDefault = 1500
AudioRateDefault = 64
FrameRateDefault = 30
VideoPresetDefault = H264PresetFast
OutputFormatDefault = AVFormatMP4

// limits
Expand Down Expand Up @@ -52,9 +64,19 @@ type RecorderConfig struct {
VideoRate int
AudioRate int
FrameRate int
VideoPreset H264Preset
OutputFormat AVFormat
}

func (p H264Preset) IsValid() bool {
switch p {
case H264PresetMedium, H264PresetFast, H264PresetFaster, H264PresetVeryFast, H264PresetSuperFast, H264PresetUltraFast:
return true
default:
return false
}
}

func (cfg RecorderConfig) IsValid() error {
if cfg == (RecorderConfig{}) {
return fmt.Errorf("config cannot be empty")
Expand Down Expand Up @@ -108,6 +130,9 @@ func (cfg RecorderConfig) IsValid() error {
if cfg.OutputFormat != AVFormatMP4 {
return fmt.Errorf("OutputFormat value is not valid")
}
if !cfg.VideoPreset.IsValid() {
return fmt.Errorf("VideoPreset value is not valid")
}

return nil
}
Expand Down Expand Up @@ -136,9 +161,88 @@ func (cfg *RecorderConfig) SetDefaults() {
if cfg.OutputFormat == "" {
cfg.OutputFormat = OutputFormatDefault
}

if cfg.VideoPreset == "" {
cfg.VideoPreset = VideoPresetDefault
}
}

func (cfg RecorderConfig) ToEnv() []string {
return []string{
fmt.Sprintf("SITE_URL=%s", cfg.SiteURL),
fmt.Sprintf("CALL_ID=%s", cfg.CallID),
fmt.Sprintf("THREAD_ID=%s", cfg.ThreadID),
fmt.Sprintf("AUTH_TOKEN=%s", cfg.AuthToken),
fmt.Sprintf("WIDTH=%d", cfg.Width),
fmt.Sprintf("HEIGHT=%d", cfg.Height),
fmt.Sprintf("VIDEO_RATE=%d", cfg.VideoRate),
fmt.Sprintf("AUDIO_RATE=%d", cfg.AudioRate),
fmt.Sprintf("FRAME_RATE=%d", cfg.FrameRate),
fmt.Sprintf("VIDEO_PRESET=%s", cfg.VideoPreset),
fmt.Sprintf("OUTPUT_FORMAT=%s", cfg.OutputFormat),
}
}

func (cfg RecorderConfig) ToMap() map[string]any {
return map[string]any{
"site_url": cfg.SiteURL,
"call_id": cfg.CallID,
"thread_id": cfg.ThreadID,
"auth_token": cfg.AuthToken,
"width": cfg.Width,
"height": cfg.Height,
"video_rate": cfg.VideoRate,
"audio_rate": cfg.AudioRate,
"frame_rate": cfg.FrameRate,
"video_preset": cfg.VideoPreset,
"output_format": cfg.OutputFormat,
}
}

func loadConfig() (RecorderConfig, error) {
func (cfg *RecorderConfig) FromMap(m map[string]any) *RecorderConfig {
cfg.SiteURL, _ = m["site_url"].(string)
cfg.CallID, _ = m["call_id"].(string)
cfg.ThreadID, _ = m["thread_id"].(string)
cfg.AuthToken, _ = m["auth_token"].(string)
if width, ok := m["width"].(float64); ok {
cfg.Width = int(width)
} else {
cfg.Width, _ = m["width"].(int)
}
if height, ok := m["height"].(float64); ok {
cfg.Height = int(height)
} else {
cfg.Height, _ = m["height"].(int)
}
if videoRate, ok := m["video_rate"].(float64); ok {
cfg.VideoRate = int(videoRate)
} else {
cfg.VideoRate, _ = m["video_rate"].(int)
}
if audioRate, ok := m["audio_rate"].(float64); ok {
cfg.AudioRate = int(audioRate)
} else {
cfg.AudioRate, _ = m["audio_rate"].(int)
}
if frameRate, ok := m["frame_rate"].(float64); ok {
cfg.FrameRate = int(frameRate)
} else {
cfg.FrameRate, _ = m["frame_rate"].(int)
}
if videoPreset, ok := m["video_preset"].(string); ok {
cfg.VideoPreset = H264Preset(videoPreset)
} else {
cfg.VideoPreset, _ = m["video_preset"].(H264Preset)
}
if outputFormat, ok := m["output_format"].(string); ok {
cfg.OutputFormat = AVFormat(outputFormat)
} else {
cfg.OutputFormat, _ = m["output_format"].(AVFormat)
}
return cfg
}

func LoadFromEnv() (RecorderConfig, error) {
var cfg RecorderConfig
cfg.SiteURL = strings.TrimSuffix(os.Getenv("SITE_URL"), "/")
cfg.CallID = os.Getenv("CALL_ID")
Expand Down Expand Up @@ -185,5 +289,13 @@ func loadConfig() (RecorderConfig, error) {
cfg.FrameRate = int(rate)
}

if val := os.Getenv("VIDEO_PRESET"); val != "" {
cfg.VideoPreset = H264Preset(val)
}

if val := os.Getenv("OUTPUT_FORMAT"); val != "" {
cfg.OutputFormat = AVFormat(val)
}

return cfg, nil
}
95 changes: 77 additions & 18 deletions cmd/recorder/config_test.go → cmd/recorder/config/config_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package config

import (
"os"
Expand Down Expand Up @@ -110,6 +110,22 @@ func TestConfigIsValid(t *testing.T) {
},
expectedError: "FrameRate value is not valid",
},
{
name: "invalid video preset",
cfg: RecorderConfig{
SiteURL: "http://localhost:8065",
CallID: "8w8jorhr7j83uqr6y1st894hqe",
ThreadID: "udzdsg7dwidbzcidx5khrf8nee",
AuthToken: "qj75unbsef83ik9p7ueypb6iyw",
Width: 1280,
Height: 720,
VideoRate: 1000,
AudioRate: 64,
FrameRate: 30,
OutputFormat: AVFormatMP4,
},
expectedError: "VideoPreset value is not valid",
},
{
name: "invalid format",
cfg: RecorderConfig{
Expand Down Expand Up @@ -137,6 +153,7 @@ func TestConfigIsValid(t *testing.T) {
VideoRate: 1000,
AudioRate: 64,
FrameRate: 30,
VideoPreset: "medium",
OutputFormat: AVFormatMP4,
},
},
Expand Down Expand Up @@ -164,6 +181,7 @@ func TestConfigSetDefaults(t *testing.T) {
VideoRate: VideoRateDefault,
AudioRate: AudioRateDefault,
FrameRate: FrameRateDefault,
VideoPreset: VideoPresetDefault,
OutputFormat: OutputFormatDefault,
}, cfg)
})
Expand All @@ -180,45 +198,46 @@ func TestConfigSetDefaults(t *testing.T) {
VideoRate: VideoRateDefault,
AudioRate: AudioRateDefault,
FrameRate: FrameRateDefault,
VideoPreset: VideoPresetDefault,
OutputFormat: OutputFormatDefault,
}, cfg)
})
}

func TestLoadConfig(t *testing.T) {
func TestLoadFromEnv(t *testing.T) {
t.Run("no env set", func(t *testing.T) {
cfg, err := loadConfig()
cfg, err := LoadFromEnv()
require.NoError(t, err)
require.Empty(t, cfg)
})

t.Run("parsing failure", func(t *testing.T) {
os.Setenv("WIDTH", "invalid")
cfg, err := loadConfig()
cfg, err := LoadFromEnv()
require.Empty(t, cfg)
require.EqualError(t, err, `failed to parse Width: strconv.ParseInt: parsing "invalid": invalid syntax`)
os.Unsetenv("WIDTH")

os.Setenv("HEIGHT", "invalid")
cfg, err = loadConfig()
cfg, err = LoadFromEnv()
require.Empty(t, cfg)
require.EqualError(t, err, `failed to parse Height: strconv.ParseInt: parsing "invalid": invalid syntax`)
os.Unsetenv("HEIGHT")

os.Setenv("VIDEO_RATE", "invalid")
cfg, err = loadConfig()
cfg, err = LoadFromEnv()
require.Empty(t, cfg)
require.EqualError(t, err, `failed to parse VideoRate: strconv.ParseInt: parsing "invalid": invalid syntax`)
os.Unsetenv("VIDEO_RATE")

os.Setenv("AUDIO_RATE", "invalid")
cfg, err = loadConfig()
cfg, err = LoadFromEnv()
require.Empty(t, cfg)
require.EqualError(t, err, `failed to parse AudioRate: strconv.ParseInt: parsing "invalid": invalid syntax`)
os.Unsetenv("AUDIO_RATE")

os.Setenv("FRAME_RATE", "invalid")
cfg, err = loadConfig()
cfg, err = LoadFromEnv()
require.Empty(t, cfg)
require.EqualError(t, err, `failed to parse FrameRate: strconv.ParseInt: parsing "invalid": invalid syntax`)
os.Unsetenv("FRAME_RATE")
Expand All @@ -243,19 +262,59 @@ func TestLoadConfig(t *testing.T) {
defer os.Unsetenv("AUDIO_RATE")
os.Setenv("FRAME_RATE", "30")
defer os.Unsetenv("FRAME_RATE")
cfg, err := loadConfig()
os.Setenv("VIDEO_PRESET", "medium")
defer os.Unsetenv("VIDEO_PRESET")
cfg, err := LoadFromEnv()
require.NoError(t, err)
require.NotEmpty(t, cfg)
require.Equal(t, RecorderConfig{
SiteURL: "http://localhost:8065",
CallID: "8w8jorhr7j83uqr6y1st894hqe",
ThreadID: "udzdsg7dwidbzcidx5khrf8nee",
AuthToken: "qj75unbsef83ik9p7ueypb6iyw",
Width: 1920,
Height: 1080,
VideoRate: 1000,
AudioRate: 64,
FrameRate: 30,
SiteURL: "http://localhost:8065",
CallID: "8w8jorhr7j83uqr6y1st894hqe",
ThreadID: "udzdsg7dwidbzcidx5khrf8nee",
AuthToken: "qj75unbsef83ik9p7ueypb6iyw",
Width: 1920,
Height: 1080,
VideoRate: 1000,
AudioRate: 64,
FrameRate: 30,
VideoPreset: H264PresetMedium,
}, cfg)
})
}

func TestRecorderConfigToEnv(t *testing.T) {
var cfg RecorderConfig
cfg.SiteURL = "http://localhost:8065"
cfg.CallID = "8w8jorhr7j83uqr6y1st894hqe"
cfg.AuthToken = "qj75unbsef83ik9p7ueypb6iyw"
cfg.ThreadID = "udzdsg7dwidbzcidx5khrf8nee"
cfg.SetDefaults()
require.Equal(t, []string{
"SITE_URL=http://localhost:8065",
"CALL_ID=8w8jorhr7j83uqr6y1st894hqe",
"THREAD_ID=udzdsg7dwidbzcidx5khrf8nee",
"AUTH_TOKEN=qj75unbsef83ik9p7ueypb6iyw",
"WIDTH=1920",
"HEIGHT=1080",
"VIDEO_RATE=1500",
"AUDIO_RATE=64",
"FRAME_RATE=30",
"VIDEO_PRESET=fast",
"OUTPUT_FORMAT=mp4",
}, cfg.ToEnv())
}

func TestRecorderConfigMap(t *testing.T) {
var cfg RecorderConfig
cfg.SiteURL = "http://localhost:8065"
cfg.CallID = "8w8jorhr7j83uqr6y1st894hqe"
cfg.AuthToken = "qj75unbsef83ik9p7ueypb6iyw"
cfg.ThreadID = "udzdsg7dwidbzcidx5khrf8nee"
cfg.SetDefaults()

t.Run("default config", func(t *testing.T) {
var c RecorderConfig
err := c.FromMap(cfg.ToMap()).IsValid()
require.NoError(t, err)
})
}
4 changes: 3 additions & 1 deletion cmd/recorder/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"os"
"os/signal"
"syscall"

"github.com/mattermost/calls-recorder/cmd/recorder/config"
)

func main() {
Expand All @@ -16,7 +18,7 @@ func main() {
log.Fatalf("failed to write pid file: %s", err)
}

cfg, err := loadConfig()
cfg, err := config.LoadFromEnv()
if err != nil {
log.Fatalf("failed to load config: %s", err)
}
Expand Down
Loading

0 comments on commit b29c211

Please sign in to comment.