Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: add test that endNumber shortens representation used #223

Merged
merged 1 commit into from
Nov 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Better logging when loading asset representation data
- Check that pre-encrypted content has the same duration for all representations
- Test that endNumber in SegmentTemplate will limit segments used

### Fixed

Expand Down
11 changes: 6 additions & 5 deletions cmd/livesim2/app/asset.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,12 +180,13 @@ func (am *assetMgr) loadRep(logger *slog.Logger, assetPath string, as *m.Adaptat
MpdTimescale: 1,
}
if !am.writeRepData {
ok, err := rp.readFromJSON(logger, am.vodFS, am.repDataDir, assetPath)
ok, err := rp.loadFromJSON(logger, am.vodFS, am.repDataDir, assetPath)
if ok {
logger.Debug("Loaded representation data from JSON", "rep", rp.ID, "asset", assetPath)
return &rp, err
}
}
logger.Debug("Loading full representation", "rep", rp.ID, "asset", assetPath)
logger.Debug("Loading full representation by reading all segments")
st := as.SegmentTemplate
if rep.SegmentTemplate != nil {
st = rep.SegmentTemplate
Expand Down Expand Up @@ -298,8 +299,8 @@ segLoop:
return &rp, err
}

// readFromJSON reads the representation data from a gzipped or plain JSON file.
func (rp *RepData) readFromJSON(logger *slog.Logger, vodFS fs.FS, repDataDir, assetPath string) (bool, error) {
// loadFromJSON reads the representation data from a gzipped or plain JSON file.
func (rp *RepData) loadFromJSON(logger *slog.Logger, vodFS fs.FS, repDataDir, assetPath string) (bool, error) {
logger = logger.With("rep", rp.ID, "assetPath", assetPath)
if repDataDir == "" {
return false, nil
Expand Down Expand Up @@ -398,7 +399,7 @@ func (rp *RepData) writeToJSON(logger *slog.Logger, repDataDir, assetPath string
if err != nil {
return err
}
logger.Debug("Wrote repData", "path", gzipPath)
logger.Info("Wrote repData", "path", gzipPath)
return nil
}

Expand Down
201 changes: 170 additions & 31 deletions cmd/livesim2/app/asset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,44 +7,130 @@ package app
import (
"log/slog"
"os"
"path"
"path/filepath"
"runtime"
"strings"
"testing"

"github.com/stretchr/testify/assert"
m "github.com/Eyevinn/dash-mpd/mpd"
"github.com/stretchr/testify/require"
)

type wantedAssetData struct {
nrReps int
loopDurationMS int
}

type wantedRepData struct {
nrSegs int
initURI string
mpdTimescale int // SegmentTemplate timescale
mediaTimescale int
duration int
}

func TestLoadAsset(t *testing.T) {
vodFS := os.DirFS("testdata")
tmpDir := t.TempDir()
am := newAssetMgr(vodFS, tmpDir, true)
logger := slog.Default()
err := am.discoverAssets(logger)
require.NoError(t, err)
// This was first time
asset, ok := am.findAsset("assets/testpic_2s")
require.True(t, ok)
require.Equal(t, 5, len(asset.Reps))
rep := asset.Reps["V300"]
assert.Equal(t, "V300/init.mp4", rep.InitURI)
assert.Equal(t, 4, len(rep.Segments))
assert.Equal(t, 90000, rep.MediaTimescale)
assert.Equal(t, 1, rep.MpdTimescale)
assert.Equal(t, 720_000, rep.duration())
assert.Equal(t, 8000, asset.LoopDurMS)
// Second time we load using gzipped repData files
am = newAssetMgr(vodFS, tmpDir, true)
err = am.discoverAssets(logger)
require.NoError(t, err)
asset, ok = am.findAsset("assets/testpic_2s")
require.True(t, ok)
require.Equal(t, 5, len(asset.Reps))
rep = asset.Reps["V300"]
assert.Equal(t, "V300/init.mp4", rep.InitURI)
assert.Equal(t, 4, len(rep.Segments))
assert.Equal(t, 90000, rep.MediaTimescale)
assert.Equal(t, 1, rep.MpdTimescale)
assert.Equal(t, 720_000, rep.duration())
assert.Equal(t, 8000, asset.LoopDurMS)
testCases := []struct {
desc string
assetPath string
segmentEndNr uint32
ad wantedAssetData
rds map[string]wantedRepData
}{
{
desc: "testpic_2s",
assetPath: "assets/testpic_2s",
segmentEndNr: 0, // Will not be used
ad: wantedAssetData{
nrReps: 5,
loopDurationMS: 8000,
},
rds: map[string]wantedRepData{
"V300": {
nrSegs: 4,
initURI: "V300/init.mp4",
mpdTimescale: 1,
mediaTimescale: 90_000,
duration: 720_000,
},
"A48": {
nrSegs: 4,
initURI: "A48/init.mp4",
mpdTimescale: 1,
mediaTimescale: 48_000,
duration: 384_000,
},
},
},
{
desc: "testpic_2s with endNumber == 2",
assetPath: "assets/testpic_2s",
segmentEndNr: 2, // Shorten representations to 2 segments via SegmentTemplate,
ad: wantedAssetData{
nrReps: 5,
loopDurationMS: 4000,
},
rds: map[string]wantedRepData{
"V300": {
nrSegs: 2,
initURI: "V300/init.mp4",
mpdTimescale: 1,
mediaTimescale: 90_000,
duration: 360_000,
},
"A48": {
nrSegs: 2,
initURI: "A48/init.mp4",
mpdTimescale: 1,
mediaTimescale: 48_000,
duration: 192_512,
},
},
},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
vodRoot := "testdata"
tmpDir := t.TempDir()
if tc.segmentEndNr > 0 {
if runtime.GOOS == "windows" {
return // Skip test on Windows since the tree copy does not work properly
}
// Copy the the asset part of testdata to a temporary directory and shorten the representations
src := path.Join(vodRoot, tc.assetPath)
dst := path.Join(tmpDir, tc.assetPath)
err := copyDir(src, dst)
require.NoError(t, err)
vodRoot = tmpDir
err = setSegmentEndNr(path.Join(vodRoot, tc.assetPath), tc.segmentEndNr)
require.NoError(t, err)
}
vodFS := os.DirFS(vodRoot)
for _, writeRepData := range []bool{true, false} {
// Write repData files the first time, and read them the second
am := newAssetMgr(vodFS, tmpDir, writeRepData)
err := am.discoverAssets(logger)
require.NoError(t, err)
asset, ok := am.findAsset(tc.assetPath)
require.True(t, ok)
require.NotNil(t, asset)
require.Equal(t, tc.ad.nrReps, len(asset.Reps))
require.Equal(t, tc.ad.loopDurationMS, asset.LoopDurMS)
for repID, wrd := range tc.rds {
rep, ok := asset.Reps[repID]
require.True(t, ok)
require.NotNil(t, rep)
require.Equal(t, wrd.nrSegs, len(rep.Segments))
require.Equal(t, wrd.initURI, rep.InitURI)
require.Equal(t, wrd.mpdTimescale, rep.MpdTimescale)
require.Equal(t, wrd.mediaTimescale, rep.MediaTimescale)
require.Equal(t, wrd.duration, rep.duration())
}
}
})
}
}

func TestAssetLookupForNameOverlap(t *testing.T) {
Expand All @@ -57,3 +143,56 @@ func TestAssetLookupForNameOverlap(t *testing.T) {
require.True(t, ok)
require.Equal(t, "assets/testpic_2s_1", a.AssetPath)
}

func copyDir(srcDir, dstDir string) error {
if err := os.MkdirAll(dstDir, 0755); err != nil {
return err
}
return filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error {
var relPath string = strings.Replace(path, srcDir, "", 1)
if relPath == "" {
return nil
}
if info.IsDir() {
return os.Mkdir(filepath.Join(dstDir, relPath), 0755)
} else {
var data, err = os.ReadFile(filepath.Join(srcDir, relPath))
if err != nil {
return err
}
return os.WriteFile(filepath.Join(dstDir, relPath), data, 0644)
}
})
}

// Set the endNumber attribute in all MPDs SegmentTemplate elements
func setSegmentEndNr(assetDir string, endNumber uint32) error {
files, err := os.ReadDir(assetDir)
if err != nil {
return err
}
for _, file := range files {
if filepath.Ext(file.Name()) == ".mpd" {
mpdPath := filepath.Join(assetDir, file.Name())

mpd, err := m.ReadFromFile(mpdPath)
if err != nil {
return err
}
p := mpd.Periods[0]
for _, a := range p.AdaptationSets {
stl := a.SegmentTemplate
stl.EndNumber = &endNumber
}
mpdRaw, err := mpd.WriteToString("", false)
if err != nil {
return err
}
err = os.WriteFile(mpdPath, []byte(mpdRaw), 0644)
if err != nil {
return err
}
}
}
return nil
}
Loading