|
|
|
@ -4,6 +4,7 @@ import (
@@ -4,6 +4,7 @@ import (
|
|
|
|
|
"io/ioutil" |
|
|
|
|
"path" |
|
|
|
|
"path/filepath" |
|
|
|
|
"strconv" |
|
|
|
|
"time" |
|
|
|
|
|
|
|
|
|
log "github.com/sirupsen/logrus" |
|
|
|
@ -11,10 +12,54 @@ import (
@@ -11,10 +12,54 @@ import (
|
|
|
|
|
"github.com/radovskyb/watcher" |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
var filesToUpload = make(map[string]string) |
|
|
|
|
type Segment struct { |
|
|
|
|
VariantIndex int // The bitrate variant
|
|
|
|
|
FullDiskPath string // Where it lives on disk
|
|
|
|
|
RelativeUploadPath string // Path it should have remotely
|
|
|
|
|
RemoteID string // Used for IPFS
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
type Variant struct { |
|
|
|
|
VariantIndex int |
|
|
|
|
Segments []Segment |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (v *Variant) getSegmentForFilename(filename string) *Segment { |
|
|
|
|
for _, segment := range v.Segments { |
|
|
|
|
if path.Base(segment.FullDiskPath) == filename { |
|
|
|
|
return &segment |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func getSegmentFromPath(fullDiskPath string) Segment { |
|
|
|
|
segment := Segment{} |
|
|
|
|
segment.FullDiskPath = fullDiskPath |
|
|
|
|
segment.RelativeUploadPath = getRelativePathFromAbsolutePath(fullDiskPath) |
|
|
|
|
index, error := strconv.Atoi(segment.RelativeUploadPath[0:1]) |
|
|
|
|
verifyError(error) |
|
|
|
|
segment.VariantIndex = index |
|
|
|
|
|
|
|
|
|
return segment |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func getVariantIndexFromPath(fullDiskPath string) int { |
|
|
|
|
index, error := strconv.Atoi(fullDiskPath[0:1]) |
|
|
|
|
verifyError(error) |
|
|
|
|
return index |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var variants []Variant |
|
|
|
|
|
|
|
|
|
func monitorVideoContent(pathToMonitor string, configuration Config, storage ChunkStorage) { |
|
|
|
|
log.Printf("Using %s files...\n", pathToMonitor) |
|
|
|
|
// Create structures to store the segments for the different stream variants
|
|
|
|
|
variants = make([]Variant, len(configuration.VideoSettings.StreamQualities)) |
|
|
|
|
for index := range variants { |
|
|
|
|
variants[index] = Variant{index, make([]Segment, 0)} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
log.Printf("Using %s for storing files with %d variants...\n", pathToMonitor, len(variants)) |
|
|
|
|
|
|
|
|
|
w := watcher.New() |
|
|
|
|
|
|
|
|
@ -22,29 +67,40 @@ func monitorVideoContent(pathToMonitor string, configuration Config, storage Chu
@@ -22,29 +67,40 @@ func monitorVideoContent(pathToMonitor string, configuration Config, storage Chu
|
|
|
|
|
for { |
|
|
|
|
select { |
|
|
|
|
case event := <-w.Event: |
|
|
|
|
if event.Op != watcher.Write { |
|
|
|
|
|
|
|
|
|
relativePath := getRelativePathFromAbsolutePath(event.Path) |
|
|
|
|
|
|
|
|
|
// Ignore removals
|
|
|
|
|
if event.Op == watcher.Remove { |
|
|
|
|
continue |
|
|
|
|
} |
|
|
|
|
if filepath.Base(event.Path) == "temp.m3u8" { |
|
|
|
|
|
|
|
|
|
for filePath, objectID := range filesToUpload { |
|
|
|
|
if objectID != "" { |
|
|
|
|
continue |
|
|
|
|
} |
|
|
|
|
// fmt.Println(event.Op, relativePath)
|
|
|
|
|
|
|
|
|
|
newObjectPath := storage.Save(path.Join(configuration.PrivateHLSPath, filePath)) |
|
|
|
|
filesToUpload[filePath] = newObjectPath |
|
|
|
|
} |
|
|
|
|
// Handle updates to the master playlist by copying it to webroot
|
|
|
|
|
if relativePath == path.Join(configuration.PrivateHLSPath, "stream.m3u8") { |
|
|
|
|
|
|
|
|
|
copy(event.Path, path.Join(configuration.PublicHLSPath, "stream.m3u8")) |
|
|
|
|
// Handle updates to playlists, but not the master playlist
|
|
|
|
|
} else if filepath.Ext(event.Path) == ".m3u8" { |
|
|
|
|
variantIndex := getVariantIndexFromPath(relativePath) |
|
|
|
|
variant := variants[variantIndex] |
|
|
|
|
|
|
|
|
|
playlistBytes, err := ioutil.ReadFile(event.Path) |
|
|
|
|
verifyError(err) |
|
|
|
|
playlistString := string(playlistBytes) |
|
|
|
|
// fmt.Println("Rewriting playlist", relativePath, "to", path.Join(configuration.PublicHLSPath, relativePath))
|
|
|
|
|
|
|
|
|
|
playlistString = storage.GenerateRemotePlaylist(playlistString, filesToUpload) |
|
|
|
|
writePlaylist(playlistString, path.Join(configuration.PublicHLSPath, "/stream.m3u8")) |
|
|
|
|
playlistString = storage.GenerateRemotePlaylist(playlistString, variant) |
|
|
|
|
|
|
|
|
|
writePlaylist(playlistString, path.Join(configuration.PublicHLSPath, relativePath)) |
|
|
|
|
} else if filepath.Ext(event.Path) == ".ts" { |
|
|
|
|
filesToUpload[filepath.Base(event.Path)] = "" |
|
|
|
|
segment := getSegmentFromPath(event.Path) |
|
|
|
|
newObjectPath := storage.Save(path.Join(configuration.PrivateHLSPath, segment.RelativeUploadPath)) |
|
|
|
|
segment.RemoteID = newObjectPath |
|
|
|
|
// fmt.Println("Uploaded", segment.RelativeUploadPath, "as", newObjectPath)
|
|
|
|
|
|
|
|
|
|
variants[segment.VariantIndex].Segments = append(variants[segment.VariantIndex].Segments, segment) |
|
|
|
|
} |
|
|
|
|
case err := <-w.Error: |
|
|
|
|
log.Fatalln(err) |
|
|
|
@ -54,8 +110,8 @@ func monitorVideoContent(pathToMonitor string, configuration Config, storage Chu
@@ -54,8 +110,8 @@ func monitorVideoContent(pathToMonitor string, configuration Config, storage Chu
|
|
|
|
|
} |
|
|
|
|
}() |
|
|
|
|
|
|
|
|
|
// Watch this folder for changes.
|
|
|
|
|
if err := w.Add(pathToMonitor); err != nil { |
|
|
|
|
// Watch the hls segment storage folder recursively for changes.
|
|
|
|
|
if err := w.AddRecursive(pathToMonitor); err != nil { |
|
|
|
|
log.Fatalln(err) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|