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