mirror of
https://github.com/datarhei/core.git
synced 2025-09-26 20:11:29 +08:00
Add ffmpeg migration tool, annotate process config with ffmpeg version constraint
This commit is contained in:
@@ -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,
|
||||
|
@@ -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,
|
||||
|
3
restream/store/fixtures/v3_empty.json
Normal file
3
restream/store/fixtures/v3_empty.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"version": 3
|
||||
}
|
@@ -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
|
||||
}
|
||||
|
@@ -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())
|
||||
}
|
||||
|
Reference in New Issue
Block a user