diff --git a/application/application.go b/application/application.go index e87ee3e..0750641 100644 --- a/application/application.go +++ b/application/application.go @@ -780,7 +780,20 @@ func (a *Application) Load(filenameOrUrl string, startTime int, contentType stri ContentId: mi.contentURL, StreamType: "BUFFERED", ContentType: mi.contentType, + Tracks: []cast.MediaTrack{ + { + TrackId: 1, + TrackContentId: mi.contentURL + "&subtitles=1", + Language: "en-US", + Subtype: "SUBTITLES", + Type: "TEXT", + TrackContentType: "text/vtt", + Name: "kundalini Subtitle", + }, + }, + TextTrackStyle: cast.TextTrackStyle{BackgroundColor: "#FFFFFF00", EdgeType: "OUTLINE", EdgeColor: "#000000FF"}, }, + ActiveTrackIds: []int{1}, }) // If we should detach from waiting for media to finish playing @@ -1079,6 +1092,7 @@ func (a *Application) startStreamingServer() error { // already been validated and is useable. filename := r.URL.Query().Get("media_file") canServe := false + isSubtitles := r.URL.Query().Get("subtitles") for _, fn := range a.mediaFilenames { if fn == filename { canServe = true @@ -1096,10 +1110,12 @@ func (a *Application) startStreamingServer() error { liveStreaming = true } - a.log("canServe=%t, liveStreaming=%t, filename=%s", canServe, liveStreaming, filename) + a.log("canServe=%t, liveStreaming=%t, filename=%s, isSubtitles=%s", canServe, liveStreaming, filename, isSubtitles) if canServe { if !liveStreaming { http.ServeFile(w, r, filename) + } else if isSubtitles != "" { + a.serveSubtitles(w, r, filename) } else { a.serveLiveStreaming(w, r, filename) } @@ -1125,12 +1141,47 @@ func (a *Application) startStreamingServer() error { return nil } +// func (a *Application) serveLiveStreamingHardsub(w http.ResponseWriter, r *http.Request, filename string) { + +// filterpath := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(filename, "\\", "/"), "[", "\\["), "]", "\\]"), ":", "\\\\:") +// cmd := exec.Command( +// "ffmpeg", +// "-hwaccel", "cuda", +// "-c:v", "h264_cuvid", +// "-i", filename, +// "-vcodec", "h264_nvenc", +// "-acodec", "aac", +// "-ac", "2", // chromecasts don't support more than two audio channels +// "-f", "mp4", +// "-vf", "subtitles="+filterpath+"", +// "-movflags", "frag_keyframe+faststart", +// "-strict", "-experimental", +// "pipe:1", +// ) + +// cmd.Stdout = w +// if a.debug { +// cmd.Stderr = os.Stderr +// } + +// w.Header().Set("Access-Control-Allow-Origin", "*") +// w.Header().Set("Transfer-Encoding", "chunked") + +// if err := cmd.Run(); err != nil { +// log.WithField("package", "application").WithFields(log.Fields{ +// "filename": filename, +// }).WithError(err).Error("error transcoding") +// } +// } + func (a *Application) serveLiveStreaming(w http.ResponseWriter, r *http.Request, filename string) { + cmd := exec.Command( "ffmpeg", - "-re", // encode at 1x playback speed, to not burn the CPU + "-hwaccel", "cuda", + "-c:v", "h264_cuvid", "-i", filename, - "-vcodec", "h264", + "-vcodec", "h264_nvenc", "-acodec", "aac", "-ac", "2", // chromecasts don't support more than two audio channels "-f", "mp4", @@ -1154,6 +1205,32 @@ func (a *Application) serveLiveStreaming(w http.ResponseWriter, r *http.Request, } } +func (a *Application) serveSubtitles(w http.ResponseWriter, r *http.Request, filename string) { + + cmd := exec.Command( + "ffmpeg", + "-i", filename, + "-c:s", "webvtt", + "-f", "webvtt", + "-strict", "-experimental", + "pipe:1", + ) + + cmd.Stdout = w + if a.debug { + cmd.Stderr = os.Stderr + } + + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Transfer-Encoding", "chunked") + + if err := cmd.Run(); err != nil { + log.WithField("package", "application").WithFields(log.Fields{ + "filename": filename, + }).WithError(err).Error("error transcoding") + } +} + func (a *Application) log(message string, args ...interface{}) { if a.debug { log.WithField("package", "application").Infof(message, args...) diff --git a/cast/payload.go b/cast/payload.go index ca02446..4cf4e4f 100644 --- a/cast/payload.go +++ b/cast/payload.go @@ -101,6 +101,8 @@ type LoadMediaCommand struct { Autoplay bool `json:"autoplay"` QueueData QueueData `json:"queueData"` CustomData interface{} `json:"customData"` + + ActiveTrackIds []int `json:"activeTrackIds"` } type QueueData struct { @@ -108,11 +110,27 @@ type QueueData struct { } type MediaItem struct { - ContentId string `json:"contentId"` - ContentType string `json:"contentType"` - StreamType string `json:"streamType"` - Duration float32 `json:"duration"` - Metadata MediaMetadata `json:"metadata"` + ContentId string `json:"contentId"` + ContentType string `json:"contentType"` + StreamType string `json:"streamType"` + Duration float32 `json:"duration"` + Metadata MediaMetadata `json:"metadata"` + Tracks []MediaTrack `json:"tracks"` + TextTrackStyle TextTrackStyle `json:"textTrackStyle"` +} +type TextTrackStyle struct { + BackgroundColor string `json:"backgroundColor"` + EdgeType string `json:"edgeType"` + EdgeColor string `json:"edgeColor"` +} +type MediaTrack struct { + TrackId int `json:"trackId"` + TrackContentId string `json:"trackContentId"` + Language string `json:"language"` + Subtype string `json:"subtype"` + Type string `json:"type"` + TrackContentType string `json:"trackContentType"` + Name string `json:"name"` } type MediaMetadata struct {