From 0bef5b35afe6fef3fe5ab7c695cba30537920265 Mon Sep 17 00:00:00 2001 From: Alex Ballas <alex@ballas.org> Date: Sat, 13 Feb 2021 01:42:34 +0200 Subject: [PATCH] Fully deprecated the goupnp/soap package. Fixed some Windows compatibility issues. Improved the subtitles support and error handling. --- flagfuncs.go | 50 ++++++++++++----- go.mod | 1 - go.sum | 4 -- go2tv.go | 1 + servefiles/servefiles.go | 8 +-- soapcalls/soapbuilders.go | 113 ++++++++++++++++++++++++++++++++++++-- soapcalls/soapcallers.go | 40 ++++++++------ 7 files changed, 173 insertions(+), 44 deletions(-) diff --git a/flagfuncs.go b/flagfuncs.go index 9abee986..fbdd1062 100644 --- a/flagfuncs.go +++ b/flagfuncs.go @@ -5,6 +5,8 @@ import ( "fmt" "net/url" "os" + "path/filepath" + "runtime" "sort" ) @@ -25,44 +27,56 @@ func listFlagFunction() error { sort.Ints(keys) for _, k := range keys { - fmt.Printf("\033[1mDevice %v\033[0m\n", k) - fmt.Printf("\033[1m--------\033[0m\n") - fmt.Printf("\033[1mModel\033[0m: %s\n", devices[k][0]) - fmt.Printf("\033[1mURL\033[0m: %s\n", devices[k][1]) + boldStart := "" + boldEnd := "" + + if runtime.GOOS == "linux" { + boldStart = "\033[1m" + boldEnd = "\033[0m" + } + fmt.Printf("%sDevice %v%s\n", boldStart, k, boldEnd) + fmt.Printf("%s--------%s\n", boldStart, boldEnd) + fmt.Printf("%sModel:%s %s\n", boldStart, boldEnd, devices[k][0]) + fmt.Printf("%sURL:%s %s\n", boldStart, boldEnd, devices[k][1]) fmt.Println() } return nil } func checkflags() (bool, error) { + if err := checkVflag(); err != nil { + return false, err + } + if err := checkTflag(); err != nil { return false, err } + list, err := checkLflag() if err != nil { return false, err } + if list == true { return true, nil } - if err := checkVflag(); err != nil { - return false, err - } - if err := checkSflag(); err != nil { return false, err } + return false, nil } func checkVflag() error { - if *videoArg == "" { - err := errors.New("No video file defined") - return err - } - if _, err := os.Stat(*videoArg); os.IsNotExist(err) { - return err + if *listPtr == false { + if *videoArg == "" { + err := errors.New("No video file defined") + return err + } + if _, err := os.Stat(*videoArg); os.IsNotExist(err) { + return err + } } return nil } @@ -72,6 +86,14 @@ func checkSflag() error { if _, err := os.Stat(*subsArg); os.IsNotExist(err) { return err } + } else { + // The checkVflag should happen before + // checkSflag so we're safe to call *videoArg + // here. If *subsArg is empty, try to + // automatically find the srt from the + // video filename. + *subsArg = (*videoArg)[0:len(*videoArg)- + len(filepath.Ext(*videoArg))] + ".srt" } return nil } diff --git a/go.mod b/go.mod index 5a6a1aae..2561dbbf 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/alexballas/go2tv go 1.15 require ( - github.com/huin/goupnp v1.0.0 github.com/koron/go-ssdp v0.0.2 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect ) diff --git a/go.sum b/go.sum index c61a2cda..e50145f8 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,7 @@ -github.com/huin/goupnp v1.0.0 h1:wg75sLpL6DZqwHQN6E1Cfk6mtfzS45z8OV+ic+DtHRo= -github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= -github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= github.com/koron/go-ssdp v0.0.2 h1:fL3wAoyT6hXHQlORyXUW4Q23kkQpJRgEAYcZB5BR71o= github.com/koron/go-ssdp v0.0.2/go.mod h1:XoLfkAiA2KeZsYh4DbHxD7h3nR2AZNqVQOa+LJuqPYs= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA= golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= diff --git a/go2tv.go b/go2tv.go index 88439070..cd64f62d 100644 --- a/go2tv.go +++ b/go2tv.go @@ -78,6 +78,7 @@ func main() { go func() { s.ServeFiles(serverStarted, absVideoFile, absSubtitlesFile) }() // Wait for HTTP server to properly initialize <-serverStarted + if err := tvdata.SendtoTV("Play"); err != nil { fmt.Fprintf(os.Stderr, "Encountered error(s): %s\n", err) os.Exit(1) diff --git a/servefiles/servefiles.go b/servefiles/servefiles.go index 340660ad..d824ed9e 100644 --- a/servefiles/servefiles.go +++ b/servefiles/servefiles.go @@ -72,14 +72,14 @@ func (f *filesToServe) serveSubtitlesHandler(w http.ResponseWriter, req *http.Re filePath, err := os.Open(f.Subtitles) defer filePath.Close() if err != nil { - fmt.Fprintf(os.Stderr, "Encountered error(s): %s\n", err) - os.Exit(1) + http.Error(w, "", 404) + return } fileStat, err := filePath.Stat() if err != nil { - fmt.Fprintf(os.Stderr, "Encountered error(s): %s\n", err) - os.Exit(1) + http.Error(w, "", 404) + return } http.ServeContent(w, req, filepath.Base(f.Subtitles), fileStat.ModTime(), filePath) diff --git a/soapcalls/soapbuilders.go b/soapcalls/soapbuilders.go index 5668a2f6..ba5dbf6c 100644 --- a/soapcalls/soapbuilders.go +++ b/soapcalls/soapbuilders.go @@ -6,18 +6,65 @@ import ( "fmt" ) -type Envelope struct { +// PlayEnvelope - As in Play Pause Stop +type PlayEnvelope struct { XMLName xml.Name `xml:"s:Envelope"` Schema string `xml:"xmlns:s,attr"` Encoding string `xml:"s:encodingStyle,attr"` - Body Body `xml:"s:Body"` + PlayBody PlayBody `xml:"s:Body"` } -type Body struct { +// PlayBody . +type PlayBody struct { + XMLName xml.Name `xml:"s:Body"` + PlayAction PlayAction `xml:"u:Play"` +} + +// PlayAction . +type PlayAction struct { + XMLName xml.Name `xml:"u:Play"` + AVTransport string `xml:"xmlns:u,attr"` + InstanceID string + Speed string +} + +// StopEnvelope - As in Play Pause Stop +type StopEnvelope struct { + XMLName xml.Name `xml:"s:Envelope"` + Schema string `xml:"xmlns:s,attr"` + Encoding string `xml:"s:encodingStyle,attr"` + StopBody StopBody `xml:"s:Body"` +} + +// StopBody . +type StopBody struct { + XMLName xml.Name `xml:"s:Body"` + StopAction StopAction `xml:"u:Stop"` +} + +// StopAction . +type StopAction struct { + XMLName xml.Name `xml:"u:Stop"` + AVTransport string `xml:"xmlns:u,attr"` + InstanceID string + Speed string +} + +// SetAVTransportEnvelope . +type SetAVTransportEnvelope struct { + XMLName xml.Name `xml:"s:Envelope"` + Schema string `xml:"xmlns:s,attr"` + Encoding string `xml:"s:encodingStyle,attr"` + Body SetAVTransportBody `xml:"s:Body"` +} + +// SetAVTransportBody . +type SetAVTransportBody struct { XMLName xml.Name `xml:"s:Body"` SetAVTransportURI SetAVTransportURI `xml:"u:SetAVTransportURI"` } +// SetAVTransportURI . type SetAVTransportURI struct { XMLName xml.Name `xml:"u:SetAVTransportURI"` AVTransport string `xml:"xmlns:u,attr"` @@ -26,11 +73,13 @@ type SetAVTransportURI struct { CurrentURIMetaData CurrentURIMetaData `xml:"CurrentURIMetaData"` } +// CurrentURIMetaData . type CurrentURIMetaData struct { XMLName xml.Name `xml:"CurrentURIMetaData"` Value []byte `xml:",chardata"` } +// DIDLLite . type DIDLLite struct { XMLName xml.Name `xml:"DIDL-Lite"` SchemaDIDL string `xml:"xmlns,attr"` @@ -40,6 +89,7 @@ type DIDLLite struct { DIDLLiteItem DIDLLiteItem `xml:"item"` } +// DIDLLiteItem . type DIDLLiteItem struct { XMLName xml.Name `xml:"item"` ID string `xml:"id,attr"` @@ -52,18 +102,21 @@ type DIDLLiteItem struct { SecCaptionInfoEx SecCaptionInfoEx `xml:"sec:CaptionInfoEx"` } +// ResNode . type ResNode struct { XMLName xml.Name `xml:"res"` ProtocolInfo string `xml:"protocolInfo,attr"` Value string `xml:",chardata"` } +// SecCaptionInfo . type SecCaptionInfo struct { XMLName xml.Name `xml:"sec:CaptionInfo"` Type string `xml:"sec:type,attr"` Value string `xml:",chardata"` } +// SecCaptionInfoEx . type SecCaptionInfoEx struct { XMLName xml.Name `xml:"sec:CaptionInfoEx"` Type string `xml:"sec:type,attr"` @@ -112,11 +165,11 @@ func setAVTransportSoapBuild(videoURL, subtitleURL string) ([]byte, error) { return make([]byte, 0), err } - d := Envelope{ + d := SetAVTransportEnvelope{ XMLName: xml.Name{}, Schema: "http://schemas.xmlsoap.org/soap/envelope/", Encoding: "http://schemas.xmlsoap.org/soap/encoding/", - Body: Body{ + Body: SetAVTransportBody{ XMLName: xml.Name{}, SetAVTransportURI: SetAVTransportURI{ XMLName: xml.Name{}, @@ -142,3 +195,53 @@ func setAVTransportSoapBuild(videoURL, subtitleURL string) ([]byte, error) { return append(xmlStart, b...), nil } + +func playSoapBuild() ([]byte, error) { + d := PlayEnvelope{ + XMLName: xml.Name{}, + Schema: "http://schemas.xmlsoap.org/soap/envelope/", + Encoding: "http://schemas.xmlsoap.org/soap/encoding/", + PlayBody: PlayBody{ + XMLName: xml.Name{}, + PlayAction: PlayAction{ + XMLName: xml.Name{}, + AVTransport: "urn:schemas-upnp-org:service:AVTransport:1", + InstanceID: "0", + Speed: "1", + }, + }, + } + xmlStart := []byte("<?xml version='1.0' encoding='utf-8'?>") + b, err := xml.Marshal(d) + if err != nil { + fmt.Println(err) + return make([]byte, 0), err + } + + return append(xmlStart, b...), nil +} + +func stopSoapBuild() ([]byte, error) { + d := StopEnvelope{ + XMLName: xml.Name{}, + Schema: "http://schemas.xmlsoap.org/soap/envelope/", + Encoding: "http://schemas.xmlsoap.org/soap/encoding/", + StopBody: StopBody{ + XMLName: xml.Name{}, + StopAction: StopAction{ + XMLName: xml.Name{}, + AVTransport: "urn:schemas-upnp-org:service:AVTransport:1", + InstanceID: "0", + Speed: "1", + }, + }, + } + xmlStart := []byte("<?xml version='1.0' encoding='utf-8'?>") + b, err := xml.Marshal(d) + if err != nil { + fmt.Println(err) + return make([]byte, 0), err + } + + return append(xmlStart, b...), nil +} diff --git a/soapcalls/soapcallers.go b/soapcalls/soapcallers.go index 69dd3a57..ae92f3c6 100644 --- a/soapcalls/soapcallers.go +++ b/soapcalls/soapcallers.go @@ -5,18 +5,8 @@ import ( "fmt" "net/http" "net/url" - - "github.com/huin/goupnp/soap" ) -type playStopRequest struct { - InstanceID string - Speed string -} - -type playStopResponse struct { -} - // TVPayload - we need this populated in order type TVPayload struct { TransportURL string @@ -54,18 +44,36 @@ func setAVTransportSoapCall(videoURL, subtitleURL, transporturl string) error { // PlayStopSoapCall - Build and call the play soap call func playStopSoapCall(action, transporturl string) error { + parsedURLtransport, err := url.Parse(transporturl) + if err != nil { + return err + } - psRequest := &playStopRequest{InstanceID: "0", Speed: "1"} - psResponse := &playStopResponse{} + var xml []byte + if action == "Play" { + xml, err = playSoapBuild() + } - parsedURL, err := url.Parse(transporturl) + if action == "Stop" { + xml, err = stopSoapBuild() + } + + client := &http.Client{} + req, err := http.NewRequest("POST", parsedURLtransport.String(), bytes.NewReader(xml)) if err != nil { return err } - newPlaycall := soap.NewSOAPClient(*parsedURL) - if err := newPlaycall.PerformAction("urn:schemas-upnp-org:service:AVTransport:1", - action, psRequest, psResponse); err != nil { + headers := http.Header{ + "SOAPAction": []string{`"urn:schemas-upnp-org:service:AVTransport:1#` + action + `"`}, + "content-type": []string{"text/xml"}, + "charset": []string{"utf-8"}, + "Connection": []string{"close"}, + } + req.Header = headers + + _, err = client.Do(req) + if err != nil { return err } return nil