Add prototyping programs
This commit is contained in:
parent
cce62f6f1a
commit
85f47cdbd8
11 changed files with 250 additions and 0 deletions
4
go.work
4
go.work
|
@ -3,3 +3,7 @@ go 1.23.4
|
|||
use ./admincli
|
||||
|
||||
use ./server
|
||||
|
||||
use ./segmentwatcher
|
||||
|
||||
use ./hlsgenerator
|
||||
|
|
7
go.work.sum
Normal file
7
go.work.sum
Normal 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
9
hlsgenerator/README.md
Normal 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
BIN
hlsgenerator/example.webm
Normal file
Binary file not shown.
3
hlsgenerator/go.mod
Normal file
3
hlsgenerator/go.mod
Normal file
|
@ -0,0 +1,3 @@
|
|||
module hlsgenerator
|
||||
|
||||
go 1.23.4
|
137
hlsgenerator/main.go
Normal file
137
hlsgenerator/main.go
Normal 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
9
segmentwatcher/README.md
Normal 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
7
segmentwatcher/go.mod
Normal 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
4
segmentwatcher/go.sum
Normal 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
70
segmentwatcher/main.go
Normal 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{})
|
||||
}
|
BIN
segmentwatcher/test-video.mkv
Normal file
BIN
segmentwatcher/test-video.mkv
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue