Skip to content

Commit

Permalink
Allow to seek when transcoding
Browse files Browse the repository at this point in the history
  • Loading branch information
alexballas committed Dec 20, 2024
1 parent 101e153 commit 0ed117e
Show file tree
Hide file tree
Showing 10 changed files with 88 additions and 17 deletions.
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
module github.com/alexballas/go2tv

go 1.23.1
go 1.23.3

require (
fyne.io/fyne/v2 v2.5.2
github.com/alexballas/chardet v0.0.0-20241220171214-9254256f9d20
github.com/alexballas/go-ssdp v0.0.3
github.com/gdamore/tcell/v2 v2.7.4
github.com/h2non/filetype v1.1.3
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/alexballas/chardet v0.0.0-20241220171214-9254256f9d20 h1:hU9GWMik9Pnz1xOIQS/LtSSDUhr+wl4ZN59fuFVWbiM=
github.com/alexballas/chardet v0.0.0-20241220171214-9254256f9d20/go.mod h1:7FYfcvOYMQ+ZZgfFK96Y70ty0VvYH7U4GXlsb8Wy3Cw=
github.com/alexballas/go-ssdp v0.0.3 h1:wTtHib45NtrkYZ/saCaB/2qJ9ci6EWeQFl6imaW4gPg=
github.com/alexballas/go-ssdp v0.0.3/go.mod h1:jUwvJl32BZxBQiRbW5Q0h1QrDGMuA+HRg5qTQvIquJE=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
Expand Down
11 changes: 8 additions & 3 deletions httphandlers/httphandlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,10 @@ func (s *HTTPserver) StartServer(serverStarted chan<- error, media, subtitles in
// Dynamically add handlers to better support gapless playback where we're
// required to serve new files with our existing HTTP server.
s.AddHandler(mURL.Path, tvpayload, media)
s.AddHandler(sURL.Path, nil, subtitles)

if sURL.Path != "/." && !tvpayload.Transcode {
s.AddHandler(sURL.Path, nil, subtitles)
}

callbackURL, err := url.Parse(tvpayload.CallbackURL)
if err != nil {
Expand Down Expand Up @@ -165,6 +168,7 @@ func (s *HTTPserver) callbackHandler(tv *soapcalls.TVPayload, screen Screen) htt

reqParsedUnescape := html.UnescapeString(string(reqParsed))
previousstate, newstate, err := soapcalls.EventNotifyParser(reqParsedUnescape)

if err != nil {
http.NotFound(w, req)
return
Expand Down Expand Up @@ -293,7 +297,7 @@ func serveContentReadClose(w http.ResponseWriter, r *http.Request, tv *soapcalls
// Since we're dealing with an io.Reader we can't
// allow any HEAD requests that some DMRs trigger.
if transcode && r.Method == http.MethodGet && strings.Contains(mediaType, "video") {
_ = utils.ServeTranscodedStream(w, f, ff, tv.FFmpegPath)
_ = utils.ServeTranscodedStream(w, f, ff, tv.FFmpegPath, tv.FFmpegSubsPath, tv.FFmpegSeek)
return
}

Expand Down Expand Up @@ -330,7 +334,8 @@ func serveContentCustomType(w http.ResponseWriter, r *http.Request, tv *soapcall
if f.path != "" {
input = f.path
}
_ = utils.ServeTranscodedStream(w, input, ff, tv.FFmpegPath)

_ = utils.ServeTranscodedStream(w, input, ff, tv.FFmpegPath, tv.FFmpegSubsPath, tv.FFmpegSeek)
return
}

Expand Down
2 changes: 2 additions & 0 deletions internal/gui/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,8 @@ func playAction(screen *NewScreen) {
Seekable: isSeek,
LogOutput: screen.Debug,
FFmpegPath: screen.ffmpegPath,
FFmpegSeek: screen.ffmpegSeek,
FFmpegSubsPath: screen.subsfile,
}

screen.httpserver = httphandlers.NewServer(whereToListen)
Expand Down
5 changes: 3 additions & 2 deletions internal/gui/gui.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ type NewScreen struct {
connectionManagerURL string
currentmfolder string
ffmpegPath string
ffmpegSeek int
mediaFormats []string
muError sync.RWMutex
mu sync.RWMutex
Expand Down Expand Up @@ -318,7 +319,7 @@ func getNextMedia(screen *NewScreen) (string, string) {
continue
}

totalMedia += 1
totalMedia++
}

for _, f := range filelist {
Expand All @@ -334,7 +335,7 @@ func getNextMedia(screen *NewScreen) (string, string) {
continue
}

counter += 1
counter++

if f.Name() == filepath.Base(screen.mediafile) {
if totalMedia == counter {
Expand Down
22 changes: 19 additions & 3 deletions internal/gui/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,13 @@ func (t *tappedSlider) Tapped(p *fyne.PointEvent) {
t.screen.CurrentPos.Set(reltime)
t.screen.EndPos.Set(end)

if t.screen.tvdata.Transcode {
t.screen.ffmpegSeek = roundedInt
stopAction(t.screen)
playAction(t.screen)

}

if err := t.screen.tvdata.SeekSoapCall(reltime); err != nil {
return
}
Expand Down Expand Up @@ -658,7 +665,7 @@ func sliderUpdate(s *NewScreen) {
continue
}

if s.State == "Stopped" || s.State == "" {
if (s.State == "Stopped" || s.State == "") && s.ffmpegSeek == 0 {
s.SlideBar.Slider.SetValue(0)
s.CurrentPos.Set("00:00:00")
s.EndPos.Set("00:00:00")
Expand All @@ -680,6 +687,15 @@ func sliderUpdate(s *NewScreen) {
continue
}

switch {
case s.ffmpegSeek > 0:
current += s.ffmpegSeek
case s.tvdata != nil && s.tvdata.FFmpegSeek > 0:
current += s.tvdata.FFmpegSeek
}

s.ffmpegSeek = 0

valueToSet := float64(current) * s.SlideBar.Max / float64(total)
if !math.IsNaN(valueToSet) {
s.SlideBar.SetValue(valueToSet)
Expand All @@ -689,12 +705,12 @@ func sliderUpdate(s *NewScreen) {
return
}

current, err := utils.FormatClockTime(getPos[1])
currentClock, err := utils.SecondsToClockTime(current)
if err != nil {
return
}

s.CurrentPos.Set(current)
s.CurrentPos.Set(currentClock)
s.EndPos.Set(end)
}
}
Expand Down
2 changes: 2 additions & 0 deletions soapcalls/soapcallers.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ type TVPayload struct {
CurrentTimers map[string]*time.Timer
InitialMediaRenderersStates map[string]bool
MediaRenderersStates map[string]*States
FFmpegSeek int
FFmpegPath string
FFmpegSubsPath string
EventURL string
ControlURL string
MediaURL string
Expand Down
21 changes: 21 additions & 0 deletions soapcalls/utils/substools.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"path/filepath"
"strconv"

"github.com/alexballas/chardet"
"github.com/mitchellh/mapstructure"
)

Expand Down Expand Up @@ -119,3 +120,23 @@ func ExtractSub(ffmpeg string, n int, f string) (string, error) {

return tempSub.Name(), nil
}

func getCharDet(path string) (string, error) {
f, err := os.Open(path)
if err != nil {
return "", err
}
defer f.Close()

firstBytes := make([]byte, 512)

n, err := f.Read(firstBytes)
if err != nil {
return "", err
}

det := chardet.NewTextDetector()
charGuess, err := det.DetectBest(firstBytes[:n])

return charGuess.Charset, err
}
19 changes: 15 additions & 4 deletions soapcalls/utils/transcode.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ package utils

import (
"errors"
"fmt"
"io"
"os/exec"
"strconv"
)

var (
Expand All @@ -15,7 +17,7 @@ var (

// ServeTranscodedStream passes an input file or io.Reader to ffmpeg and writes the output directly
// to our io.Writer.
func ServeTranscodedStream(w io.Writer, input interface{}, ff *exec.Cmd, ffmpeg string) error {
func ServeTranscodedStream(w io.Writer, input interface{}, ff *exec.Cmd, ffmpegPath, subs string, seekSeconds int) error {
// Pipe streaming is not great as explained here
// https://video.stackexchange.com/questions/34087/ffmpeg-fails-on-pipe-to-pipe-video-decoding.
// That's why if we have the option to pass the file directly to ffmpeg, we should.
Expand All @@ -33,17 +35,26 @@ func ServeTranscodedStream(w io.Writer, input interface{}, ff *exec.Cmd, ffmpeg
_ = ff.Process.Kill()
}

vf := fmt.Sprintf("format=yuv420p")
charenc, err := getCharDet(subs)
if err == nil {
vf = fmt.Sprintf("subtitles='%s':charenc=%s,format=yuv420p", subs, charenc)
}

fmt.Println(vf)
cmd := exec.Command(
ffmpeg,
ffmpegPath,
"-re",
"-ss", strconv.Itoa(seekSeconds),
"-copyts",
"-i", in,
"-vcodec", "h264",
"-acodec", "aac",
"-ac", "2",
"-vf", "format=yuv420p",
"-vf", vf,
"-preset", "ultrafast",
"-movflags", "+faststart",
"-f", "flv",
"-f", "mpegts",
"pipe:1",
)

Expand Down
18 changes: 14 additions & 4 deletions soapcalls/utils/transcode_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package utils

import (
"errors"
"fmt"
"io"
"os/exec"
"strconv"
"syscall"
)

Expand All @@ -13,7 +15,7 @@ var (

// ServeTranscodedStream passes an input file or io.Reader to ffmpeg and writes the output directly
// to our io.Writer.
func ServeTranscodedStream(w io.Writer, input interface{}, ff *exec.Cmd, ffmpeg string) error {
func ServeTranscodedStream(w io.Writer, input interface{}, ff *exec.Cmd, ffmpegPath, subs string, seekSeconds int) error {
// Pipe streaming is not great as explained here
// https://video.stackexchange.com/questions/34087/ffmpeg-fails-on-pipe-to-pipe-video-decoding.
// That's why if we have the option to pass the file directly to ffmpeg, we should.
Expand All @@ -31,17 +33,25 @@ func ServeTranscodedStream(w io.Writer, input interface{}, ff *exec.Cmd, ffmpeg
_ = ff.Process.Kill()
}

vf := fmt.Sprintf("format=yuv420p")
charenc, err := getCharDet(subs)
if err == nil {
vf = fmt.Sprintf("subtitles='%s':charenc=%s,format=yuv420p", subs, charenc)
}

cmd := exec.Command(
ffmpeg,
ffmpegPath,
"-re",
"-ss", strconv.Itoa(seekSeconds),
"-copyts",
"-i", in,
"-vcodec", "h264",
"-acodec", "aac",
"-ac", "2",
"-vf", "format=yuv420p",
"-vf", vf,
"-preset", "ultrafast",
"-movflags", "+faststart",
"-f", "flv",
"-f", "mpegts",
"pipe:1",
)

Expand Down

0 comments on commit 0ed117e

Please sign in to comment.