Live video on the AT Protocol
1package main
2
3import (
4 "encoding/json"
5 "errors"
6 "flag"
7 "fmt"
8 "io"
9 "log"
10 "math"
11 "os"
12 "time"
13
14 "stream.place/streamplace/pkg/rtcrec"
15)
16
17func main() {
18 err := Start()
19 if err != nil {
20 log.Fatal(err)
21 }
22}
23
24func Start() error {
25 if len(os.Args) > 1 && os.Args[1] == "decode" {
26 return Decode()
27 }
28 if len(os.Args) > 1 && os.Args[1] == "trim" {
29 return Trim()
30 }
31 return fmt.Errorf("unknown command: %s", os.Args[1])
32}
33
34func Trim() error {
35 var startDuration time.Duration
36 flag.DurationVar(&startDuration, "start", 0, "timestamp where we should start our clip")
37 var endDuration time.Duration
38 flag.DurationVar(&endDuration, "end", 0, "timestamp where we should end our clip")
39 var inPath string
40 flag.StringVar(&inPath, "in-path", "", "path to the file to decode")
41 var outPath string
42 flag.StringVar(&outPath, "out-path", "", "path to the file to write the trimmed file to")
43 err := flag.CommandLine.Parse(os.Args[2:])
44 if err != nil {
45 return err
46 }
47 if startDuration == 0 && endDuration == 0 {
48 return fmt.Errorf("start or end duration is required (otherwise, you know, the cp command is right there)")
49 }
50 if inPath == "" {
51 return fmt.Errorf("in-path is required")
52 }
53 if outPath == "" {
54 return fmt.Errorf("out-path is required")
55 }
56 inFile, err := os.Open(inPath)
57 if err != nil {
58 return err
59 }
60 defer inFile.Close()
61 outFile, err := os.Create(outPath)
62 if err != nil {
63 return err
64 }
65 defer outFile.Close()
66 dec, err := rtcrec.MakeWebRTCDecoder(inFile)
67 if err != nil {
68 return err
69 }
70 encoder, err := rtcrec.MakeWebRTCEncoder(outFile)
71 if err != nil {
72 return err
73 }
74 var startCutoff *time.Time
75 var endCutoff *time.Time
76 if startDuration == 0 {
77 startCutoff = &time.Time{}
78 }
79 if endDuration == 0 {
80 t := time.Unix(math.MaxInt64, 0)
81 endCutoff = &t
82 }
83 included := 0
84 dropped := 0
85 for {
86 ev, err := dec.Next()
87 if errors.Is(err, io.EOF) {
88 break
89 }
90 if startCutoff == nil {
91 t := ev.Time.Add(startDuration)
92 startCutoff = &t
93 }
94 if endCutoff == nil {
95 t := ev.Time.Add(endDuration)
96 endCutoff = &t
97 }
98 // we only rewrite trackread events
99 if ev.TrackRead == nil {
100 if ev.Time.Before(*endCutoff) {
101 // included++
102 encoder.Event(*ev)
103 }
104 continue
105 }
106 if ev.Time.Before(*startCutoff) || ev.Time.After(*endCutoff) {
107 // fmt.Printf("dropped: %s < %s\n", ev.Time.Format(time.RFC3339Nano), cutoff.Format(time.RFC3339Nano))
108 dropped++
109 continue
110 }
111 included++
112 ev.Time = ev.Time.Add(-startDuration)
113 encoder.Event(*ev)
114 }
115 fmt.Printf("included: %d, dropped: %d\n", included, dropped)
116 return nil
117}
118
119func Decode() error {
120 var path string
121 flag.StringVar(&path, "path", "", "path to the file to decode")
122 flag.Parse()
123 if path == "" {
124 return fmt.Errorf("path is required")
125 }
126 return DecodeFile(path)
127}
128
129func DecodeFile(path string) error {
130 f, err := os.Open(path)
131 if err != nil {
132 return err
133 }
134 defer f.Close()
135 dec, err := rtcrec.MakeWebRTCDecoder(f)
136 if err != nil {
137 return err
138 }
139 for {
140 ev, err := dec.Next()
141 if errors.Is(err, io.EOF) {
142 return nil
143 }
144 if err != nil {
145 return err
146 }
147 if ev.TrackRead != nil {
148 // spitting out the data as base64 is pointless, replace with a label
149 n := len(ev.TrackRead.Data)
150 byteString := fmt.Sprintf("%d bytes", n)
151 bs, err := json.Marshal(ev)
152 if err != nil {
153 return err
154 }
155 var m map[string]any
156 err = json.Unmarshal(bs, &m)
157 if err != nil {
158 return err
159 }
160 m["trackRead"].(map[string]any)["data"] = byteString
161 bs, err = json.Marshal(m)
162 if err != nil {
163 return err
164 }
165 fmt.Println(string(bs))
166 } else {
167 bs, err := json.Marshal(ev)
168 if err != nil {
169 return err
170 }
171 fmt.Println(string(bs))
172 }
173 }
174}