Changes to support updating the ffmpeg path from the settings tab

This commit is contained in:
Alex Ballas
2024-08-24 15:20:25 +03:00
parent e85a5b50c6
commit 7eccb00288
15 changed files with 241 additions and 38 deletions

View File

@@ -252,11 +252,11 @@ func serveContent(w http.ResponseWriter, r *http.Request, tv *soapcalls.TVPayloa
switch f := mf.(type) {
case osFileType:
serveContentCustomType(w, r, mediaType, transcode, seek, f, ff)
serveContentCustomType(w, r, tv, mediaType, transcode, seek, f, ff)
case []byte:
serveContentBytes(w, r, mediaType, f)
case io.ReadCloser:
serveContentReadClose(w, r, mediaType, transcode, f, ff)
serveContentReadClose(w, r, tv, mediaType, transcode, f, ff)
default:
http.NotFound(w, r)
return
@@ -279,7 +279,7 @@ func serveContentBytes(w http.ResponseWriter, r *http.Request, mediaType string,
http.ServeContent(w, r, name, time.Now(), bReader)
}
func serveContentReadClose(w http.ResponseWriter, r *http.Request, mediaType string, transcode bool, f io.ReadCloser, ff *exec.Cmd) {
func serveContentReadClose(w http.ResponseWriter, r *http.Request, tv *soapcalls.TVPayload, mediaType string, transcode bool, f io.ReadCloser, ff *exec.Cmd) {
if r.Header.Get("getcontentFeatures.dlna.org") == "1" {
contentFeatures, err := utils.BuildContentFeatures(mediaType, "00", transcode)
if err != nil {
@@ -293,7 +293,7 @@ func serveContentReadClose(w http.ResponseWriter, r *http.Request, mediaType str
// 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)
_ = utils.ServeTranscodedStream(w, f, ff, tv.FFmpegPath)
return
}
@@ -305,7 +305,7 @@ func serveContentReadClose(w http.ResponseWriter, r *http.Request, mediaType str
}
}
func serveContentCustomType(w http.ResponseWriter, r *http.Request, mediaType string, transcode, seek bool, f osFileType, ff *exec.Cmd) {
func serveContentCustomType(w http.ResponseWriter, r *http.Request, tv *soapcalls.TVPayload, mediaType string, transcode, seek bool, f osFileType, ff *exec.Cmd) {
if r.Header.Get("getcontentFeatures.dlna.org") == "1" {
seekflag := "00"
if seek {
@@ -330,7 +330,7 @@ func serveContentCustomType(w http.ResponseWriter, r *http.Request, mediaType st
if f.path != "" {
input = f.path
}
_ = utils.ServeTranscodedStream(w, input, ff)
_ = utils.ServeTranscodedStream(w, input, ff, tv.FFmpegPath)
return
}

View File

@@ -100,8 +100,7 @@ func selectMediaFile(screen *NewScreen, f fyne.URI) {
screen.MediaText.Refresh()
subs, err := utils.GetSubs(absMediaFile)
check(screen, err)
subs, err := utils.GetSubs(screen.ffmpegPath, absMediaFile)
if err != nil {
screen.SelectInternalSubs.Options = []string{}
screen.SelectInternalSubs.PlaceHolder = "No Embedded Subs"
@@ -344,7 +343,7 @@ func playAction(screen *NewScreen) {
if opt == screen.SelectInternalSubs.Selected {
screen.PlayPause.Text = "Extracting Subtitles"
screen.PlayPause.Refresh()
tempSubsPath, err := utils.ExtractSub(n, screen.mediafile)
tempSubsPath, err := utils.ExtractSub(screen.ffmpegPath, n, screen.mediafile)
screen.PlayPause.Text = "Play"
screen.PlayPause.Refresh()
if err != nil {
@@ -373,6 +372,7 @@ func playAction(screen *NewScreen) {
Transcode: screen.Transcode,
Seekable: isSeek,
LogOutput: screen.Debug,
FFmpegPath: screen.ffmpegPath,
}
screen.httpserver = httphandlers.NewServer(whereToListen)

View File

@@ -21,6 +21,7 @@ import (
"fyne.io/fyne/v2/widget"
"github.com/alexballas/go2tv/httphandlers"
"github.com/alexballas/go2tv/soapcalls"
"github.com/alexballas/go2tv/soapcalls/utils"
)
// NewScreen .
@@ -41,6 +42,7 @@ type NewScreen struct {
SubsText *widget.Entry
CustomSubsCheck *widget.Check
NextMediaCheck *widget.Check
TranscodeCheckBox *widget.Check
Stop *widget.Button
DeviceList *deviceList
httpserver *httphandlers.HTTPserver
@@ -60,6 +62,7 @@ type NewScreen struct {
renderingControlURL string
connectionManagerURL string
currentmfolder string
ffmpegPath string
mediaFormats []string
muError sync.RWMutex
mu sync.RWMutex
@@ -113,6 +116,11 @@ func Start(ctx context.Context, s *NewScreen) {
s.Hotkeys = true
tabs.OnSelected = func(t *container.TabItem) {
s.TranscodeCheckBox.Enable()
if err := utils.CheckFFmpeg(s.ffmpegPath); err != nil {
s.TranscodeCheckBox.Disable()
}
if t.Text == "Go2TV" {
s.Hotkeys = true
return
@@ -120,6 +128,10 @@ func Start(ctx context.Context, s *NewScreen) {
s.Hotkeys = false
}
if err := utils.CheckFFmpeg(s.ffmpegPath); err != nil {
s.TranscodeCheckBox.Disable()
}
s.tabs = tabs
w.SetContent(tabs)

View File

@@ -8,7 +8,6 @@ import (
"errors"
"math"
"net/url"
"os/exec"
"sort"
"sync"
"time"
@@ -325,11 +324,6 @@ func mainWindow(s *NewScreen) fyne.CanvasObject {
nextmedia := widget.NewCheck("Auto-Play Next File", func(b bool) {})
transcode := widget.NewCheck("Transcode", func(b bool) {})
_, err := exec.LookPath("ffmpeg")
if err != nil {
transcode.Disable()
}
mediafilelabel := canvas.NewText("File:", nil)
subsfilelabel := canvas.NewText("Subtitles:", nil)
devicelabel := canvas.NewText("Select Device:", nil)
@@ -367,6 +361,7 @@ func mainWindow(s *NewScreen) fyne.CanvasObject {
s.CurrentPos = curPos
s.EndPos = endPos
s.SelectInternalSubs = selectInternalSubs
s.TranscodeCheckBox = transcode
curPos.Set("00:00:00")
endPos.Set("00:00:00")
@@ -476,7 +471,7 @@ func mainWindow(s *NewScreen) fyne.CanvasObject {
s.MediaText.Disable()
if mediafileOld != "" {
subs, err := utils.GetSubs(mediafileOld)
subs, err := utils.GetSubs(s.ffmpegPath, mediafileOld)
if err != nil {
s.SelectInternalSubs.Options = []string{}
s.SelectInternalSubs.PlaceHolder = "No Embedded Subs"
@@ -531,7 +526,7 @@ func mainWindow(s *NewScreen) fyne.CanvasObject {
}
if s.tvdata != nil && s.tvdata.CallbackURL != "" {
_, err = queueNext(s, true)
_, err := queueNext(s, true)
if err != nil {
stopAction(s)
}

View File

@@ -4,6 +4,7 @@
package gui
import (
"runtime"
"time"
"fyne.io/fyne/v2"
@@ -43,6 +44,35 @@ func settingsWindow(s *NewScreen) fyne.CanvasObject {
}
}
ffmpegText := widget.NewLabel("ffmpeg Path")
ffmpegTextEntry := widget.NewEntry()
ffmpegTextEntry.Text = func() string {
if fyne.CurrentApp().Preferences().String("ffmpeg") != "" {
return fyne.CurrentApp().Preferences().String("ffmpeg")
}
os := runtime.GOOS
switch os {
case "windows":
return "ffmpeg"
case "linux":
return "ffmpeg"
case "darwin":
return "/opt/homebrew/bin/ffmpeg"
default:
return "ffmpeg"
}
}()
ffmpegTextEntry.Refresh()
s.ffmpegPath = ffmpegTextEntry.Text
ffmpegTextEntry.OnChanged = func(update string) {
s.ffmpegPath = update
fyne.CurrentApp().Preferences().SetString("ffmpeg", update)
}
debugText := widget.NewLabel("Debug")
debugExport := widget.NewButton("Export Debug Logs", func() {
var itemInRing bool
@@ -107,7 +137,7 @@ If 'Auto-Play Next File' is not working correctly, please disable it.`, w)
dropdown.Refresh()
return container.New(layout.NewFormLayout(), themeText, dropdown, gaplessText, gaplessdropdown, debugText, debugExport)
return container.New(layout.NewFormLayout(), themeText, dropdown, gaplessText, gaplessdropdown, ffmpegText, ffmpegTextEntry, debugText, debugExport)
}
func saveDebugLogs(f fyne.URIWriteCloser, s *NewScreen) {

View File

@@ -369,7 +369,7 @@ func setAVTransportSoapBuild(tvdata *TVPayload) ([]byte, error) {
var didl didLLiteItem
resNodeData := []resNode{}
duration, _ := utils.DurationForMedia(tvdata.MediaPath)
duration, _ := utils.DurationForMedia(tvdata.FFmpegPath, tvdata.MediaPath)
switch duration {
case "":
@@ -512,7 +512,7 @@ func setNextAVTransportSoapBuild(tvdata *TVPayload, clear bool) ([]byte, error)
var didl didLLiteItem
resNodeData := []resNode{}
duration, _ := utils.DurationForMedia(tvdata.MediaPath)
duration, _ := utils.DurationForMedia(tvdata.FFmpegPath, tvdata.MediaPath)
switch duration {
case "":

View File

@@ -40,6 +40,7 @@ type TVPayload struct {
CurrentTimers map[string]*time.Timer
InitialMediaRenderersStates map[string]bool
MediaRenderersStates map[string]*States
FFmpegPath string
EventURL string
ControlURL string
MediaURL string

View File

@@ -0,0 +1,15 @@
//go:build !windows
// +build !windows
package utils
import "os/exec"
func CheckFFmpeg(ffmpeg string) error {
checkffmpeg := exec.Command(ffmpeg, "-h")
_, err := checkffmpeg.Output()
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,17 @@
package utils
import (
"os/exec"
"syscall"
)
func CheckFFmpeg(ffmpeg string) error {
checkffmpeg := exec.Command(ffmpeg, "-h")
checkffmpeg.SysProcAttr = &syscall.SysProcAttr{CreationFlags: 0x08000000}
_, err := checkffmpeg.Output()
if err != nil {
return err
}
return nil
}

View File

@@ -8,6 +8,7 @@ import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strconv"
"time"
)
@@ -18,14 +19,18 @@ type ffprobeInfo struct {
} `json:"format"`
}
func DurationForMedia(f string) (string, error) {
func DurationForMedia(ffmpeg string, f string) (string, error) {
_, err := os.Stat(f)
if err != nil {
return "", err
}
if err := CheckFFmpeg(ffmpeg); err != nil {
return "", err
}
cmd := exec.Command(
"ffprobe",
filepath.Join(filepath.Dir(ffmpeg), "ffprobe"),
"-loglevel", "error",
"-show_format",
"-of", "json",

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strconv"
"syscall"
"time"
@@ -16,14 +17,18 @@ type ffprobeInfo struct {
} `json:"format"`
}
func DurationForMedia(f string) (string, error) {
func DurationForMedia(ffmpeg string, f string) (string, error) {
_, err := os.Stat(f)
if err != nil {
return "", err
}
if err := CheckFFmpeg(ffmpeg); err != nil {
return "", err
}
cmd := exec.Command(
"ffprobe",
filepath.Join(filepath.Dir(ffmpeg), "ffprobe"),
"-loglevel", "error",
"-show_format",
"-of", "json",

View File

@@ -1,11 +1,14 @@
//go:build !windows
// +build !windows
package utils
import (
"encoding/json"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"strconv"
"github.com/mitchellh/mapstructure"
@@ -28,15 +31,20 @@ type tags struct {
var ErrNoSubs = errors.New("no subs")
func GetSubs(f string) ([]string, error) {
func GetSubs(ffmpeg string, f string) ([]string, error) {
_, err := os.Stat(f)
if err != nil {
fmt.Println(err)
return nil, err
}
// We assume the ffprobe path based on the ffmpeg one.
// So we need to ensure that the ffmpeg one exists.
if err := CheckFFmpeg(ffmpeg); err != nil {
return nil, err
}
cmd := exec.Command(
"ffprobe",
filepath.Join(filepath.Dir(ffmpeg), "ffprobe"),
"-loglevel", "error",
"-show_streams",
"-of", "json",
@@ -45,14 +53,12 @@ func GetSubs(f string) ([]string, error) {
output, err := cmd.Output()
if err != nil {
fmt.Println(err)
return nil, err
}
var info ffprobeInfoforSubs
if err := json.Unmarshal(output, &info); err != nil {
fmt.Println(err)
return nil, err
}
@@ -64,7 +70,6 @@ func GetSubs(f string) ([]string, error) {
subcounter++
tag := &tags{}
if err := mapstructure.Decode(s.Tags, tag); err != nil {
fmt.Println(err)
return nil, err
}
@@ -82,14 +87,13 @@ func GetSubs(f string) ([]string, error) {
}
if len(out) == 0 {
fmt.Println(ErrNoSubs)
return nil, ErrNoSubs
}
return out, nil
}
func ExtractSub(n int, f string) (string, error) {
func ExtractSub(ffmpeg string, n int, f string) (string, error) {
_, err := os.Stat(f)
if err != nil {
return "", err
@@ -101,7 +105,7 @@ func ExtractSub(n int, f string) (string, error) {
}
cmd := exec.Command(
"ffmpeg",
ffmpeg,
"-y",
"-i", f,
"-map", "0:s:"+strconv.Itoa(n),

View File

@@ -0,0 +1,119 @@
package utils
import (
"encoding/json"
"errors"
"os"
"os/exec"
"path/filepath"
"strconv"
"syscall"
"github.com/mitchellh/mapstructure"
)
type ffprobeInfoforSubs struct {
Streams []streams `json:"streams"`
}
type streams struct {
Tags any `json:"tags,omitempty"`
CodecType string `json:"codec_type"`
Index int `json:"index"`
}
type tags struct {
Title string `mapstructure:"title"`
Language string `mapstructure:"language"`
}
var ErrNoSubs = errors.New("no subs")
func GetSubs(ffmpeg string, f string) ([]string, error) {
_, err := os.Stat(f)
if err != nil {
return nil, err
}
if err := CheckFFmpeg(ffmpeg); err != nil {
return nil, err
}
cmd := exec.Command(
filepath.Join(filepath.Dir(ffmpeg), "ffprobe"),
"-loglevel", "error",
"-show_streams",
"-of", "json",
f,
)
cmd.SysProcAttr = &syscall.SysProcAttr{CreationFlags: 0x08000000}
output, err := cmd.Output()
if err != nil {
return nil, err
}
var info ffprobeInfoforSubs
if err := json.Unmarshal(output, &info); err != nil {
return nil, err
}
out := make([]string, 0)
var subcounter int
for _, s := range info.Streams {
if s.CodecType == "subtitle" {
subcounter++
tag := &tags{}
if err := mapstructure.Decode(s.Tags, tag); err != nil {
return nil, err
}
subName := tag.Title
if tag.Title == "" {
subName = tag.Language
}
if subName == "" {
subName = strconv.Itoa(subcounter)
}
out = append(out, subName)
}
}
if len(out) == 0 {
return nil, ErrNoSubs
}
return out, nil
}
func ExtractSub(ffmpeg string, n int, f string) (string, error) {
_, err := os.Stat(f)
if err != nil {
return "", err
}
tempSub, err := os.CreateTemp(os.TempDir(), "go2tv-sub-*.srt")
if err != nil {
return "", err
}
cmd := exec.Command(
ffmpeg,
"-y",
"-i", f,
"-map", "0:s:"+strconv.Itoa(n),
tempSub.Name(),
)
cmd.SysProcAttr = &syscall.SysProcAttr{CreationFlags: 0x08000000}
_, err = cmd.Output()
if err != nil {
return "", err
}
return tempSub.Name(), nil
}

View File

@@ -15,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) error {
func ServeTranscodedStream(w io.Writer, input interface{}, ff *exec.Cmd, ffmpeg string) 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.
@@ -34,7 +34,7 @@ func ServeTranscodedStream(w io.Writer, input interface{}, ff *exec.Cmd) error {
}
cmd := exec.Command(
"ffmpeg",
ffmpeg,
"-re",
"-i", in,
"-vcodec", "h264",

View File

@@ -13,7 +13,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) error {
func ServeTranscodedStream(w io.Writer, input interface{}, ff *exec.Cmd, ffmpeg string) 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.
@@ -32,7 +32,7 @@ func ServeTranscodedStream(w io.Writer, input interface{}, ff *exec.Cmd) error {
}
cmd := exec.Command(
"ffmpeg",
ffmpeg,
"-re",
"-i", in,
"-vcodec", "h264",