Skip to content

Commit

Permalink
Merge pull request #49 from alexballas/devel
Browse files Browse the repository at this point in the history
1.12.0
  • Loading branch information
alexballas authored Jul 2, 2022
2 parents 889c2f3 + 876b4af commit 701fe64
Show file tree
Hide file tree
Showing 21 changed files with 1,217 additions and 153 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
build
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ Cast your media files to UPnP/DLNA Media Renderers and Smart TVs.
---
GUI mode
-----
![](https://i.imgur.com/0zcre1d.png)
![](https://i.imgur.com/WLcqEKt.png)
![](https://i.imgur.com/nrfIc81.png)
![](https://i.imgur.com/ksCaCFl.png)

CLI mode
-----
Expand Down Expand Up @@ -53,7 +53,7 @@ Usage of go2tv:

Allowed media files in the GUI
-----
- mp4, avi, mkv, mpeg, mov, webm, m4v, mpv, mp3, flac, wav
- mp4, avi, mkv, mpeg, mov, webm, m4v, mpv, mp3, flac, wav, jpg, jpeg, png

This is a GUI only limitation.

Expand Down
323 changes: 323 additions & 0 deletions cmd/go2tv-lite/go2tv.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,323 @@
package main

import (
"context"
_ "embed"
"flag"
"fmt"
"io"
"net/url"
"os"
"os/exec"
"path/filepath"
"runtime"
"sort"
"strings"
"sync"
"time"

"github.com/alexballas/go2tv/devices"
"github.com/alexballas/go2tv/httphandlers"
"github.com/alexballas/go2tv/internal/interactive"
"github.com/alexballas/go2tv/soapcalls"
"github.com/alexballas/go2tv/utils"
"github.com/pkg/errors"
)

var (
//go:embed version.txt
version string
errNoflag = errors.New("no flag used")
mediaArg = flag.String("v", "", "Local path to the video/audio file. (Triggers the CLI mode)")
urlArg = flag.String("u", "", "HTTP URL to the media file. URL streaming does not support seek operations. (Triggers the CLI mode)")
subsArg = flag.String("s", "", "Local path to the subtitles file.")
targetPtr = flag.String("t", "", "Cast to a specific UPnP/DLNA Media Renderer URL.")
transcodePtr = flag.Bool("tc", false, "Use ffmpeg to transcode input video file.")
listPtr = flag.Bool("l", false, "List all available UPnP/DLNA Media Renderer models and URLs.")
versionPtr = flag.Bool("version", false, "Print version.")
)

type flagResults struct {
dmrURL string
exit bool
}

func main() {
var absMediaFile string
var mediaType string
var mediaFile interface{}
var isSeek bool

flag.Parse()

flagRes, err := processflags()
check(err)

if flagRes.exit {
os.Exit(0)
}

if *mediaArg != "" {
mediaFile = *mediaArg
}

if *mediaArg == "" && *urlArg != "" {
mediaURL, err := utils.StreamURL(context.Background(), *urlArg)
check(err)

mediaURLinfo, err := utils.StreamURL(context.Background(), *urlArg)
check(err)

mediaType, err = utils.GetMimeDetailsFromStream(mediaURLinfo)
check(err)

mediaFile = mediaURL

if strings.Contains(mediaType, "image") {
readerToBytes, err := io.ReadAll(mediaURL)
mediaURL.Close()
check(err)
mediaFile = readerToBytes
}
}

switch t := mediaFile.(type) {
case string:
absMediaFile, err = filepath.Abs(t)
check(err)

mfile, err := os.Open(absMediaFile)
check(err)

mediaFile = absMediaFile
mediaType, err = utils.GetMimeDetailsFromFile(mfile)

if !*transcodePtr {
isSeek = true
}

check(err)
case io.ReadCloser, []byte:
absMediaFile = *urlArg
}

absSubtitlesFile, err := filepath.Abs(*subsArg)
check(err)

upnpServicesURLs, err := soapcalls.DMRextractor(flagRes.dmrURL)
check(err)

whereToListen, err := utils.URLtoListenIPandPort(flagRes.dmrURL)
check(err)

scr, err := interactive.InitTcellNewScreen()
check(err)

callbackPath, err := utils.RandomString()
check(err)

tvdata := &soapcalls.TVPayload{
ControlURL: upnpServicesURLs.AvtransportControlURL,
EventURL: upnpServicesURLs.AvtransportEventSubURL,
RenderingControlURL: upnpServicesURLs.RenderingControlURL,
CallbackURL: "http://" + whereToListen + "/" + callbackPath,
MediaURL: "http://" + whereToListen + "/" + utils.ConvertFilename(absMediaFile),
SubtitlesURL: "http://" + whereToListen + "/" + utils.ConvertFilename(absSubtitlesFile),
MediaType: mediaType,
CurrentTimers: make(map[string]*time.Timer),
MediaRenderersStates: make(map[string]*soapcalls.States),
InitialMediaRenderersStates: make(map[string]bool),
RWMutex: &sync.RWMutex{},
Transcode: *transcodePtr,
Seekable: isSeek,
}

s := httphandlers.NewServer(whereToListen)
serverStarted := make(chan struct{})

// We pass the tvdata here as we need the callback handlers to be able to react
// to the different media renderer states.
go func() {
err := s.StartServer(serverStarted, mediaFile, absSubtitlesFile, tvdata, scr)
check(err)
}()
// Wait for HTTP server to properly initialize
<-serverStarted

scr.InterInit(tvdata)
}

func check(err error) {
if errors.Is(err, errNoflag) {
flag.Usage()
os.Exit(0)
}

if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Encountered error(s): %s\n", err)
os.Exit(1)
}
}

func listFlagFunction() error {
flagsEnabled := 0
flag.Visit(func(f *flag.Flag) {
flagsEnabled++
})

if flagsEnabled > 1 {
return errors.New("cant combine -l with other flags")
}

deviceList, err := devices.LoadSSDPservices(1)
if err != nil {
return errors.New("failed to list devices")
}

fmt.Println()

// We loop through this map twice as we need to maintain
// the correct order.
keys := make([]string, 0)
for k := range deviceList {
keys = append(keys, k)
}

sort.Strings(keys)

for q, k := range keys {
boldStart := ""
boldEnd := ""

if runtime.GOOS == "linux" {
boldStart = "\033[1m"
boldEnd = "\033[0m"
}
fmt.Printf("%sDevice %v%s\n", boldStart, q+1, boldEnd)
fmt.Printf("%s--------%s\n", boldStart, boldEnd)
fmt.Printf("%sModel:%s %s\n", boldStart, boldEnd, k)
fmt.Printf("%sURL:%s %s\n", boldStart, boldEnd, deviceList[k])
fmt.Println()
}

return nil
}

func processflags() (*flagResults, error) {
checkVerflag()

res := &flagResults{}

if *mediaArg == "" && !*listPtr && *urlArg == "" {
return nil, fmt.Errorf("checkflags error: %w", errNoflag)
}

if err := checkTCflag(res); err != nil {
return nil, fmt.Errorf("checkflags error: %w", err)
}

if err := checkTflag(res); err != nil {
return nil, fmt.Errorf("checkflags error: %w", err)
}

list, err := checkLflag()
if err != nil {
return nil, fmt.Errorf("checkflags error: %w", err)
}

if list {
res.exit = true
return res, nil
}

if err := checkVflag(); err != nil {
return nil, fmt.Errorf("checkflags error: %w", err)
}

if err := checkSflag(); err != nil {
return nil, fmt.Errorf("checkflags error: %w", err)
}

return res, nil
}

func checkVflag() error {
if !*listPtr && *urlArg == "" {
if _, err := os.Stat(*mediaArg); os.IsNotExist(err) {
return fmt.Errorf("checkVflags error: %w", err)
}
}

return nil
}

func checkSflag() error {
if *subsArg != "" {
if _, err := os.Stat(*subsArg); os.IsNotExist(err) {
return fmt.Errorf("checkSflags error: %w", err)
}
return nil
}

// The checkVflag should happen before checkSflag so we're safe to call
// *mediaArg here. If *subsArg is empty, try to automatically find the
// srt from the media file filename.
*subsArg = (*mediaArg)[0:len(*mediaArg)-
len(filepath.Ext(*mediaArg))] + ".srt"

return nil
}

func checkTCflag(res *flagResults) error {
if *transcodePtr {
_, err := exec.LookPath("ffmpeg")
if err != nil {
return fmt.Errorf("checkTCflag parse error: %w", err)
}
}

return nil
}

func checkTflag(res *flagResults) error {
if *targetPtr != "" {
// Validate URL before proceeding.
_, err := url.ParseRequestURI(*targetPtr)
if err != nil {
return fmt.Errorf("checkTflag parse error: %w", err)
}

res.dmrURL = *targetPtr
return nil
}

deviceList, err := devices.LoadSSDPservices(1)
if err != nil {
return fmt.Errorf("checkTflag service loading error: %w", err)
}

res.dmrURL, err = devices.DevicePicker(deviceList, 1)
if err != nil {
return fmt.Errorf("checkTflag device picker error: %w", err)
}

return nil
}

func checkLflag() (bool, error) {
if *listPtr {
if err := listFlagFunction(); err != nil {
return false, fmt.Errorf("checkLflag error: %w", err)
}
return true, nil
}

return false, nil
}

func checkVerflag() {
if *versionPtr && os.Args[1] == "-version" {
fmt.Printf("Go2TV Version: %s\n", version)
os.Exit(0)
}
}
1 change: 1 addition & 0 deletions cmd/go2tv-lite/version.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.12.0
16 changes: 10 additions & 6 deletions cmd/go2tv/go2tv.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ func main() {
var absMediaFile string
var mediaType string
var mediaFile interface{}
var isSeek bool

flag.Parse()

Expand Down Expand Up @@ -95,8 +96,12 @@ func main() {
check(err)

mediaFile = absMediaFile

mediaType, err = utils.GetMimeDetailsFromFile(mfile)

if !*transcodePtr {
isSeek = true
}

check(err)
case io.ReadCloser, []byte:
absMediaFile = *urlArg
Expand Down Expand Up @@ -130,6 +135,7 @@ func main() {
InitialMediaRenderersStates: make(map[string]bool),
RWMutex: &sync.RWMutex{},
Transcode: *transcodePtr,
Seekable: isSeek,
}

s := httphandlers.NewServer(whereToListen)
Expand Down Expand Up @@ -255,11 +261,9 @@ func checkSflag() error {
return nil
}

// The checkVflag should happen before
// checkSflag so we're safe to call *mediaArg
// here. If *subsArg is empty, try to
// automatically find the srt from the
// media file filename.
// The checkVflag should happen before checkSflag so we're safe to call
// *mediaArg here. If *subsArg is empty, try to automatically find the
// srt from the media file filename.
*subsArg = (*mediaArg)[0:len(*mediaArg)-
len(filepath.Ext(*mediaArg))] + ".srt"

Expand Down
2 changes: 1 addition & 1 deletion cmd/go2tv/version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
devel
1.12.0
Loading

0 comments on commit 701fe64

Please sign in to comment.