Live video on the AT Protocol
1package media
2
3import (
4 "context"
5 "crypto/sha256"
6 "encoding/json"
7 "fmt"
8 "io"
9 "os"
10 "path/filepath"
11 "reflect"
12 "sort"
13 "strings"
14 "testing"
15
16 "github.com/stretchr/testify/require"
17 "stream.place/streamplace/pkg/aqio"
18 "stream.place/streamplace/pkg/config"
19 ct "stream.place/streamplace/pkg/config/configtesting"
20 "stream.place/streamplace/pkg/crypto/spkey"
21 "stream.place/streamplace/test/remote"
22)
23
24var testTimestamp = "2025-01-01T00:00:00.000Z"
25
26func makeServerMediaSigner(t *testing.T) *MediaSignerLocal {
27 priv, _, err := spkey.GenerateStreamKey()
28 require.NoError(t, err)
29 require.NoError(t, err)
30 signer, err := spkey.KeyToSigner(priv)
31 require.NoError(t, err)
32 cli := ct.CLI(t, &config.CLI{
33 TAURL: "http://timestamp.digicert.com",
34 WideOpen: true,
35 })
36 msInterface, err := MakeMediaSigner(context.Background(), cli, "test-person", signer, nil)
37 require.NoError(t, err)
38 ms := msInterface.(*MediaSignerLocal)
39 return ms
40}
41
42func TestSegmentRoundtrip(t *testing.T) {
43 testCases := []struct {
44 name string
45 fixture string
46 }{
47 // {
48 // name: "OneMinute",
49 // fixture: remote.RemoteArchive("4563c7b48c0ca02c3fc87bbe6f1e63a743656e465a82bec0af75ef7eead04a23/1-minute-of-signed-segments.tar.gz"),
50 // },
51 {
52 name: "ThreeSegs",
53 fixture: remote.RemoteArchive("c21e9352e72ca0729c66af2fcabec1b8997b509601241e8d38d5728f9687386b/threesegs.tar.gz"),
54 },
55 }
56 for _, testCase := range testCases {
57 t.Run(testCase.name, func(t *testing.T) {
58 withNoGSTLeaks(t, func() {
59 tempDir, err := os.MkdirTemp("", "ingredient_test")
60 t.Logf("tempDir: %s", tempDir)
61 require.NoError(t, err)
62 getTestVids := func() []io.ReadSeeker {
63 testVids := []io.ReadSeeker{}
64 segEntries, err := os.ReadDir(testCase.fixture)
65 require.NoError(t, err)
66 for _, segEntry := range segEntries {
67 if segEntry.Type().IsRegular() {
68 if !strings.HasSuffix(segEntry.Name(), ".mp4") {
69 continue
70 }
71 fd, err := os.Open(filepath.Join(testCase.fixture, segEntry.Name()))
72 require.NoError(t, err)
73 testVids = append(testVids, fd)
74 }
75 }
76 return testVids
77 }
78
79 firstReport, err := makeSegDirReport(t, testCase.fixture)
80 require.NoError(t, err)
81 ms := makeServerMediaSigner(t)
82 rws := aqio.NewReadWriteSeeker([]byte{})
83 err = CombineSegments(context.Background(), getTestVids(), ms, rws)
84 require.NoError(t, err)
85
86 _, err = rws.Seek(0, io.SeekStart)
87 require.NoError(t, err)
88
89 signedSplitSegDir := makeTestSubdir(t, tempDir, "signed-split-segments")
90 cli := &config.CLI{}
91 fs := cli.NewFlagSet("rtcrec-test")
92 err = cli.Parse(fs, []string{})
93 require.NoError(t, err)
94 err = SplitSegments(context.Background(), cli, rws, func(fname string) ReadWriteSeekCloser {
95 fd, err := os.Create(filepath.Join(signedSplitSegDir, fname))
96 require.NoError(t, err)
97 return fd
98 })
99 require.NoError(t, err)
100 secondReport, err := makeSegDirReport(t, signedSplitSegDir)
101 require.NoError(t, err)
102 require.NoError(t, firstReport.CheckEquals(secondReport), "signed split segments are not equal to original segments")
103 })
104 })
105 }
106}
107
108func makeTestSubdir(t *testing.T, tempDir, subdir string) string {
109 subDir := filepath.Join(tempDir, subdir)
110 err := os.MkdirAll(subDir, 0755)
111 require.NoError(t, err)
112 return subDir
113}
114
115type SegDirReport struct {
116 Dir string
117 Segs []string
118 Hashes []string
119}
120
121func makeSegDirReport(t *testing.T, segDir string) (*SegDirReport, error) {
122 segs := []string{}
123 segEntries, err := os.ReadDir(segDir)
124 require.NoError(t, err)
125 for _, segEntry := range segEntries {
126 if segEntry.Type().IsRegular() {
127 segPath := filepath.Join(segDir, segEntry.Name())
128 segs = append(segs, segPath)
129 }
130 }
131 sort.Strings(segs)
132 hashes := make([]string, len(segs))
133 for i, segPath := range segs {
134 hash, err := hashFile(segPath)
135 if err != nil {
136 return nil, err
137 }
138 hashes[i] = hash
139 }
140
141 return &SegDirReport{
142 Dir: segDir,
143 Segs: segs,
144 Hashes: hashes,
145 }, nil
146}
147
148func (s *SegDirReport) Equals(other *SegDirReport) bool {
149 if len(s.Segs) != len(other.Segs) {
150 return false
151 }
152 if len(s.Hashes) != len(other.Hashes) {
153 return false
154 }
155 return reflect.DeepEqual(s.Hashes, other.Hashes)
156}
157
158func hashFile(path string) (string, error) {
159 bs, err := os.ReadFile(path)
160 if err != nil {
161 return "", err
162 }
163 hash := sha256.Sum256(bs)
164 return fmt.Sprintf("%x", hash), nil
165}
166
167func (s *SegDirReport) CheckEquals(other *SegDirReport) error {
168 if !s.Equals(other) {
169 str1 := s.ToString()
170 str2 := other.ToString()
171 return fmt.Errorf("files should be equal: %s\n%s", str1, str2)
172 }
173 return nil
174}
175
176func (s *SegDirReport) ToString() string {
177 bs, err := json.MarshalIndent(s, "", " ")
178 if err != nil {
179 panic(err)
180 }
181 return string(bs)
182}