Add ffmpeg migration tool, annotate process config with ffmpeg version constraint

This commit is contained in:
Ingo Oppermann
2022-11-02 22:02:39 +01:00
parent 4cc82dd333
commit dfc81ac38f
34 changed files with 2307 additions and 86 deletions

View File

@@ -37,6 +37,7 @@ func (io ConfigIO) Clone() ConfigIO {
type Config struct {
ID string `json:"id"`
Reference string `json:"reference"`
FFVersion string `json:"ffversion"`
Input []ConfigIO `json:"input"`
Output []ConfigIO `json:"output"`
Options []string `json:"options"`
@@ -53,6 +54,7 @@ func (config *Config) Clone() *Config {
clone := &Config{
ID: config.ID,
Reference: config.Reference,
FFVersion: config.FFVersion,
Reconnect: config.Reconnect,
ReconnectDelay: config.ReconnectDelay,
Autostart: config.Autostart,

View File

@@ -24,6 +24,8 @@ import (
rfs "github.com/datarhei/core/v16/restream/fs"
"github.com/datarhei/core/v16/restream/replace"
"github.com/datarhei/core/v16/restream/store"
"github.com/Masterminds/semver/v3"
)
// The Restreamer interface
@@ -267,7 +269,18 @@ func (r *restream) load() error {
tasks := make(map[string]*task)
skills := r.ffmpeg.Skills()
ffversion := skills.FFmpeg.Version
if v, err := semver.NewVersion(ffversion); err == nil {
// Remove the patch level for the constraint
ffversion = fmt.Sprintf("%d.%d.0", v.Major(), v.Minor())
}
for id, process := range data.Process {
if len(process.Config.FFVersion) == 0 {
process.Config.FFVersion = "^" + ffversion
}
t := &task{
id: id,
reference: process.Reference,
@@ -295,6 +308,23 @@ func (r *restream) load() error {
// replaced, we can resolve references and validate the
// inputs and outputs.
for _, t := range tasks {
// Just warn if the ffmpeg version constraint doesn't match the available ffmpeg version
if c, err := semver.NewConstraint(t.config.FFVersion); err == nil {
if v, err := semver.NewVersion(skills.FFmpeg.Version); err == nil {
if !c.Check(v) {
r.logger.Warn().WithFields(log.Fields{
"id": t.id,
"constraint": t.config.FFVersion,
"version": skills.FFmpeg.Version,
}).WithError(fmt.Errorf("available FFmpeg version doesn't fit constraint; you have to update this process to adjust the constraint")).Log("")
}
} else {
r.logger.Warn().WithField("id", t.id).WithError(err).Log("")
}
} else {
r.logger.Warn().WithField("id", t.id).WithError(err).Log("")
}
err := r.resolveAddresses(tasks, t.config)
if err != nil {
r.logger.Warn().WithField("id", t.id).WithError(err).Log("Ignoring")
@@ -407,6 +437,12 @@ func (r *restream) createTask(config *app.Config) (*task, error) {
return nil, fmt.Errorf("an empty ID is not allowed")
}
config.FFVersion = "^" + r.ffmpeg.Skills().FFmpeg.Version
if v, err := semver.NewVersion(config.FFVersion); err == nil {
// Remove the patch level for the constraint
config.FFVersion = fmt.Sprintf("^%d.%d.0", v.Major(), v.Minor())
}
process := &app.Process{
ID: config.ID,
Reference: config.Reference,

View File

@@ -0,0 +1,3 @@
{
"version": 3
}

View File

@@ -4,6 +4,7 @@ import (
gojson "encoding/json"
"fmt"
"os"
"path"
"sync"
"github.com/datarhei/core/v16/encoding/json"
@@ -12,14 +13,15 @@ import (
)
type JSONConfig struct {
Dir string
Logger log.Logger
Filepath string
FFVersion string
Logger log.Logger
}
type jsonStore struct {
filename string
dir string
logger log.Logger
filepath string
ffversion string
logger log.Logger
// Mutex to serialize access to the backend
lock sync.RWMutex
@@ -29,13 +31,13 @@ var version uint64 = 4
func NewJSONStore(config JSONConfig) Store {
s := &jsonStore{
filename: "db.json",
dir: config.Dir,
logger: config.Logger,
filepath: config.Filepath,
ffversion: config.FFVersion,
logger: config.Logger,
}
if s.logger == nil {
s.logger = log.New("JSONStore")
s.logger = log.New("")
}
return s
@@ -45,7 +47,7 @@ func (s *jsonStore) Load() (StoreData, error) {
s.lock.Lock()
defer s.lock.Unlock()
data, err := s.load(version)
data, err := s.load(s.filepath, version)
if err != nil {
return NewStoreData(), err
}
@@ -63,7 +65,7 @@ func (s *jsonStore) Store(data StoreData) error {
s.lock.RLock()
defer s.lock.RUnlock()
err := s.store(data)
err := s.store(s.filepath, data)
if err != nil {
return fmt.Errorf("failed to store data: %w", err)
}
@@ -71,13 +73,16 @@ func (s *jsonStore) Store(data StoreData) error {
return nil
}
func (s *jsonStore) store(data StoreData) error {
func (s *jsonStore) store(filepath string, data StoreData) error {
jsondata, err := gojson.MarshalIndent(&data, "", " ")
if err != nil {
return err
}
tmpfile, err := os.CreateTemp(s.dir, s.filename)
dir := path.Dir(filepath)
name := path.Base(filepath)
tmpfile, err := os.CreateTemp(dir, name)
if err != nil {
return err
}
@@ -92,13 +97,11 @@ func (s *jsonStore) store(data StoreData) error {
return err
}
filename := s.dir + "/" + s.filename
if err := file.Rename(tmpfile.Name(), filename); err != nil {
if err := file.Rename(tmpfile.Name(), filepath); err != nil {
return err
}
s.logger.WithField("file", filename).Debug().Log("Stored data")
s.logger.WithField("file", filepath).Debug().Log("Stored data")
return nil
}
@@ -107,12 +110,10 @@ type storeVersion struct {
Version uint64 `json:"version"`
}
func (s *jsonStore) load(version uint64) (StoreData, error) {
func (s *jsonStore) load(filepath string, version uint64) (StoreData, error) {
r := NewStoreData()
filename := s.dir + "/" + s.filename
_, err := os.Stat(filename)
_, err := os.Stat(filepath)
if err != nil {
if os.IsNotExist(err) {
return r, nil
@@ -121,7 +122,7 @@ func (s *jsonStore) load(version uint64) (StoreData, error) {
return r, err
}
jsondata, err := os.ReadFile(filename)
jsondata, err := os.ReadFile(filepath)
if err != nil {
return r, err
}
@@ -140,7 +141,7 @@ func (s *jsonStore) load(version uint64) (StoreData, error) {
return r, json.FormatError(jsondata, err)
}
s.logger.WithField("file", filename).Debug().Log("Read data")
s.logger.WithField("file", filepath).Debug().Log("Read data")
return r, nil
}

View File

@@ -1,10 +1,9 @@
package store
import (
"os"
"testing"
"github.com/datarhei/core/v16/log"
"github.com/stretchr/testify/require"
)
@@ -15,34 +14,71 @@ func TestNew(t *testing.T) {
}
func TestLoad(t *testing.T) {
store := &jsonStore{
filename: "v4_empty.json",
dir: "./fixtures",
logger: log.New(""),
}
store := NewJSONStore(JSONConfig{
Filepath: "./fixtures/v4_empty.json",
})
_, err := store.Load()
require.Equal(t, nil, err)
}
func TestLoadFailed(t *testing.T) {
store := &jsonStore{
filename: "v4_invalid.json",
dir: "./fixtures",
logger: log.New(""),
}
store := NewJSONStore(JSONConfig{
Filepath: "./fixtures/v4_invalid.json",
})
_, err := store.Load()
require.NotEqual(t, nil, err)
}
func TestIsEmpty(t *testing.T) {
store := &jsonStore{
filename: "v4_empty.json",
dir: "./fixtures",
logger: log.New(""),
}
store := NewJSONStore(JSONConfig{
Filepath: "./fixtures/v4_empty.json",
})
data, _ := store.Load()
data, err := store.Load()
require.NoError(t, err)
require.Equal(t, true, data.IsEmpty())
}
func TestNotExists(t *testing.T) {
store := NewJSONStore(JSONConfig{
Filepath: "./fixtures/v4_notexist.json",
})
data, err := store.Load()
require.NoError(t, err)
require.Equal(t, true, data.IsEmpty())
}
func TestStore(t *testing.T) {
os.Remove("./fixtures/v4_store.json")
store := NewJSONStore(JSONConfig{
Filepath: "./fixtures/v4_store.json",
})
data, err := store.Load()
require.NoError(t, err)
require.Equal(t, true, data.IsEmpty())
data.Metadata.System["somedata"] = "foobar"
store.Store(data)
data2, err := store.Load()
require.NoError(t, err)
require.Equal(t, data, data2)
os.Remove("./fixtures/v4_store.json")
}
func TestInvalidVersion(t *testing.T) {
store := NewJSONStore(JSONConfig{
Filepath: "./fixtures/v3_empty.json",
})
data, err := store.Load()
require.Error(t, err)
require.Equal(t, true, data.IsEmpty())
}