Add prototyping programs

This commit is contained in:
batteredbunny 2025-02-02 17:10:23 +02:00
parent cce62f6f1a
commit 85f47cdbd8
11 changed files with 250 additions and 0 deletions

View file

@ -3,3 +3,7 @@ go 1.23.4
use ./admincli use ./admincli
use ./server use ./server
use ./segmentwatcher
use ./hlsgenerator

7
go.work.sum Normal file
View file

@ -0,0 +1,7 @@
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

9
hlsgenerator/README.md Normal file
View file

@ -0,0 +1,9 @@
# HLSGenerator
Simple test program that manages hls segments itself and lays them out in a m3u8 playlist
```sh
mkdir -p segments
ffmpeg -i example.webm -c:v libx264 -c:a aac -b:a 128k -ac 2 -pix_fmt yuv420p -preset ultrafas
t -hls_time 1 -f hls segments/index.m3u8
```

BIN
hlsgenerator/example.webm Normal file

Binary file not shown.

3
hlsgenerator/go.mod Normal file
View file

@ -0,0 +1,3 @@
module hlsgenerator
go 1.23.4

137
hlsgenerator/main.go Normal file
View file

@ -0,0 +1,137 @@
package main
import (
"fmt"
"log"
"os"
"os/exec"
"strconv"
"strings"
"time"
)
var streamFileName = "stream.m3u8"
var segmentsFolder = "segments"
var maxSegments = 10
type State struct {
UpdateCount int
Segments []Segment
LiveSegments []Segment
}
func (s *State) ParseSegments() {
entries, err := os.ReadDir(segmentsFolder)
if err != nil {
log.Fatalln("Failed to open segments directory: ", err)
return
}
for _, entry := range entries {
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".ts") {
continue
}
name := fmt.Sprintf("%s/%s", segmentsFolder, entry.Name())
duration, err := FfprobeGetDuration(name)
if err != nil {
log.Fatal("Failed to parse duration: ", err)
}
fmt.Println("Found segment:", name, "d:", duration)
s.Segments = append(s.Segments, Segment{
Length: duration,
FileName: name,
})
}
s.LiveSegments = s.Segments
}
// Example program for generating a livestream of a loading spinner using precut hls segments
func main() {
state := State{}
state.ParseSegments()
firstPass := true
for {
for i, segment := range state.LiveSegments {
if !firstPass {
// Adds current segment to end of playlist
state.LiveSegments = append(state.LiveSegments, state.Segments[i%len(state.Segments)])
}
fmt.Println("Adding segment", segment.FileName)
if firstPass {
UpdateFile(&state.UpdateCount, state.LiveSegments[:i+1])
} else {
// Limits playlist to 10 segments
if len(state.LiveSegments) > maxSegments {
state.LiveSegments = state.LiveSegments[i+1:]
}
UpdateFile(&state.UpdateCount, state.LiveSegments)
}
d, err := segment.Duration()
if err != nil {
log.Fatal(err)
}
fmt.Println("Sleeping for", d)
time.Sleep(d)
}
if firstPass {
fmt.Println("First pass over")
firstPass = false
}
fmt.Println("Starting loop over")
}
}
type Segment struct {
Length float64
FileName string
}
func (s *Segment) Duration() (time.Duration, error) {
d, err := time.ParseDuration(fmt.Sprintf("%fs", s.Length))
if err != nil {
return 0, err
}
return d, nil
}
func FfprobeGetDuration(filename string) (float64, error) {
cmd := exec.Command("ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", filename)
output, err := cmd.Output()
if err != nil {
return -1, err
}
return strconv.ParseFloat(strings.TrimSpace(string(output)), 64)
}
func (s Segment) String() string {
return fmt.Sprintf("#EXTINF:%f\n%s\n", s.Length, s.FileName)
}
func UpdateFile(updateCount *int, segments []Segment) {
var segmentsFmt string
for _, segment := range segments {
segmentsFmt += segment.String()
}
stream := fmt.Sprintf(`#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:10
#EXT-X-MEDIA-SEQUENCE:%d
%s`, *updateCount, segmentsFmt)
os.WriteFile(streamFileName, []byte(stream), 0644)
*updateCount++
}

9
segmentwatcher/README.md Normal file
View file

@ -0,0 +1,9 @@
# segmentwatcher
Example program
```
mkdir -p hls
ffmpeg -re -i test-video.mkv -hls_flags delete_segments+append_list+omit_endlist -f hls hls/
index.m3u8
```

7
segmentwatcher/go.mod Normal file
View file

@ -0,0 +1,7 @@
module segmentwatcher
go 1.23.4
require github.com/fsnotify/fsnotify v1.8.0
require golang.org/x/sys v0.29.0 // indirect

4
segmentwatcher/go.sum Normal file
View file

@ -0,0 +1,4 @@
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=

70
segmentwatcher/main.go Normal file
View file

@ -0,0 +1,70 @@
package main
import (
"log"
"slices"
"strings"
"github.com/fsnotify/fsnotify"
)
var segmentList = []string{}
func remove(slice []string, s int) []string {
return append(slice[:s], slice[s+1:]...)
}
func printSegmentList() {
log.Printf("Segment list: %s\n", strings.Join(segmentList, ", "))
}
func main() {
log.Println("Starting segment watcher")
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Fatal(err)
}
defer watcher.Close()
// Start listening for events.
go func() {
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return
}
if strings.HasSuffix(event.Name, ".ts") {
if event.Op == fsnotify.Create {
segmentList = append(segmentList, event.Name)
log.Println("Created file", event.Name)
printSegmentList()
} else if event.Op == fsnotify.Remove {
index := slices.Index(segmentList, event.Name)
if index != -1 {
segmentList = remove(segmentList, index)
}
log.Println("Removed file", event.Name)
printSegmentList()
}
}
case err, ok := <-watcher.Errors:
if !ok {
return
}
log.Println("error:", err)
}
}
}()
// Add a path.
err = watcher.Add("hls")
if err != nil {
log.Fatal(err)
}
// Block main goroutine forever.
<-make(chan struct{})
}

Binary file not shown.