Skip to content
This repository has been archived by the owner on Nov 18, 2023. It is now read-only.

drop youtube_dl in favour of pytube #11

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ fmt:
go fmt . ./apps ./apps/youtube ./apps/youtube/mp ./config ./log ./server

run: build
../../bin/plaincast
${GOPATH}bin/plaincast

install:
cp ../../bin/plaincast /usr/local/bin/plaincast.new
cp ${GOPATH}bin/plaincast /usr/local/bin/plaincast.new
mv /usr/local/bin/plaincast.new /usr/local/bin/plaincast
if ! egrep -q "^plaincast:" /etc/passwd; then useradd -s /bin/false -r -M plaincast -g audio; fi
mkdir -p /var/local/plaincast
Expand Down
97 changes: 59 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ This is a small [DIAL](http://www.dial-multiscreen.org) server that emulates
Chromecast-like devices, and implements the YouTube app. It only renders the
audio, not the video, so it is very lightweight and can run headless.

It can be used as media server, for example on the [Raspberry
Pi](http://www.raspberrypi.org/).
It can be used as media server, for example on the [Raspberry Pi](http://www.raspberrypi.org/).

## Installation

Expand All @@ -17,11 +16,12 @@ First, make sure you have the needed dependencies installed:

* golang 1.3 (1.1+ might also work, but 1.0 certainly doesn't)
* libmpv-dev
* youtube-dl (see also 'notes on youtube-dl' below)
* pip3
* pytube (see 'notes on pytube' below)

These can be installed in one go under Debian Jessie:

$ sudo apt-get install golang libmpv-dev youtube-dl
$ sudo apt-get install golang libmpv-dev python3-pip

If you haven't already set up a Go workspace, create one now. Some people like
to set it to their home directory, but you can also set it to a separate
Expand All @@ -34,57 +34,78 @@ directory. In any case, set the environment variable `$GOROOT` to this path:
Then get the required packages and compile:

$ go get -u github.com/aykevl/plaincast

To run the server, you can run the executable `bin/plaincast` relative to your Go
workspace.

To run the server, run the executable `bin/plaincast` relative to your Go
workspace. Any Android phone with YouTube app (or possibly iPhone, but I haven't
tested) on the same network should recognize the server and it should be
possible to play the audio of videos on it. The Chrome extension doesn't yet
work.
$ bin/plaincast [OPTIONS]

$ bin/plaincast
or install it as service

## Notes on youtube-dl
$ cd src/github.com/aykevl/plaincast
$ make install

`youtube-dl` is often too old to be used for downloading YouTube streams. You
can try to run `youtube-dl -U`, but it may say that it won't update because it
has been installed via a package manager. To fix this, uninstall youtube-dl, and
install it via pip. The steps required depend on the version of Python in your
`$PATH` variable. Check it with:
If you want to remove service `$ make remove`

$ python --version
Any browser that supports chromecast extension and Android phone with YouTube app
(or possibly iPhone, but I haven't tested) on the same network should recognize
the server and it should be possible to play the audio of videos on it.

Install using pip for **Python 2** (usually version 2.7.x), on Debian stretch
and below:

$ sudo apt-get remove youtube-dl
$ sudo apt-get install python-pip
$ sudo pip2 install youtube-dl
### Manual service installation

Install using pip3 for **Python 3** (version 3.x). Only required when you have
configured the `python` binary to point to Python 3, or maybe on newer versions
of Debian.
Copy compiled binary file `plaincast` to `/usr/local/bin/` and create new user *plaincast* in group *audio*

$ sudo apt-get remove youtube-dl
$ sudo apt-get install python3-pip
$ sudo pip3 install youtube-dl
$ useradd -s /bin/false -r -M plaincast -g audio
Create directory

Afterwards, you can update youtube-dl using:
$ mkdir -p /var/local/plaincast
$ chown plaincast:audio /var/local/plaincast

$ sudo pip install --upgrade youtube-dl
Copy systemd unit file `plaincast.service` to `/etc/systemd/system/` and enable the service

Or for Python 3:
`$ systemctl enable plaincast`

$ sudo pip3 install --upgrade youtube-dl

It is advisable to run this regularly as it has to keep up with YouTube updates.
Certainly first try updating youtube-dl when plaincast stops working.
## Options
-h, -help Prints help text and exit
-ao-pcm Write audio to a file, 48kHz stereo format S16
-app Name of the app to run on startup, no need to use
as currently is supported only YouTube
-cachedir Cache directory location for youtube-dl
-config Location of the configuration file, path to to config
(default location ~/.config/plaincast.json)
-friendly-name Custom friendly name (default "Plaincast HOSTNAME")
-http-port Custom http port (default 8008)
-log-libmpv Log output of libmpv
-log-mpv Log MPV wrapper output
-log-player Log media player messages
-log-server Log HTTP and SSDP server
-log-youtube Log YouTube app
-loglevel Baseline loglevel (info, warn, err) (default "warn")
-no-config Disable reading from and writing to config file
-no-sspd Disable SSDP server


### Snapcast support

You can easily write audio output to snapcast pipe using option

`-ao-pcm PATH-TO-SNAPFIFO`

## Known issues

* So far, only DIAL is implemented, so the Chrome extension for Chromecast
doesn't work yet (I suspect it uses mDNS, which is the successor of DIAL on
Chromecast).
## Notes on pytube

Because of youtube_dl beeing awfully slow at fetching stream urls on my raspberry pi 2 I opted
for this alternative approach, which works much faster on this low power platform.

I tried using python 2 but had no success with it. To install most recent pytube version use pip3!

$ python3 -m pip install git+https://github.com/nficano/pytube

It is advisable to run this regularly as it has to keep up with YouTube updates.
Certainly first try updating pytube when plaincast stops working.

## Thanks

Expand Down
1 change: 1 addition & 0 deletions apps/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ type App interface {
Running() bool
Quit()
FriendlyName() string // return a human-readable name
Data(string) string // return data from app
}
2 changes: 1 addition & 1 deletion apps/youtube/mp/mp.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"github.com/aykevl/plaincast/log"
)

var logger = log.New("player", "log media player messages")
var logger = log.New("player", "Log media player messages")

var cacheDir = flag.String("cachedir", "", "Cache directory")

Expand Down
24 changes: 21 additions & 3 deletions apps/youtube/mp/mpv.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,16 @@ type MPV struct {
mainloopExit chan struct{}
}

var mpvLogger = log.New("mpv", "log MPV wrapper output")
var logLibMPV = flag.Bool("log-libmpv", false, "log output of libmpv")
var mpvLogger = log.New("mpv", "Log MPV wrapper output")
var logLibMPV = flag.Bool("log-libmpv", false, "Log output of libmpv")
var flagPCM = flag.String("ao-pcm", "", "Write audio to a file, 48kHz stereo format S16")
var httpPort string

// New creates a new MPV instance and initializes the libmpv player
func (mpv *MPV) initialize() (chan State, int) {

httpPort = flag.Lookup("http-port").Value.String()

if mpv.handle != nil || mpv.running {
panic("already initialized")
}
Expand Down Expand Up @@ -70,6 +75,19 @@ func (mpv *MPV) initialize() (chan State, int) {
mpv.setOptionString("vo", "null")
mpv.setOptionString("vid", "no")


if *flagPCM != "" {
logger.Println("Writing sound to file:", *flagPCM)

mpv.setOptionString("audio-channels", "stereo")
mpv.setOptionString("audio-samplerate", "48000")
mpv.setOptionString("audio-format", "s16")
mpv.setOptionString("ao", "pcm")
mpv.setOptionString("ao-pcm-waveheader", "no")
mpv.setOptionString("ao-pcm-append", "yes")
mpv.setOptionString("ao-pcm-file", *flagPCM)
}

// Cache settings assume 128kbps audio stream (16kByte/s).
// The default is a cache size of 25MB, these are somewhat more sensible
// cache sizes IMO.
Expand Down Expand Up @@ -231,7 +249,7 @@ func (mpv *MPV) play(stream string, position time.Duration, volume int) {
if !strings.HasPrefix(stream, "https://") {
logger.Panic("Stream does not start with https://...")
}
mpv.sendCommand([]string{"loadfile", "http://localhost:8008/proxy/" + stream[len("https://"):], "replace", options})
mpv.sendCommand([]string{"loadfile", "http://localhost:" + httpPort + "/proxy/" + stream[len("https://"):], "replace", options})
}

func (mpv *MPV) pause() {
Expand Down
34 changes: 11 additions & 23 deletions apps/youtube/mp/youtube.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,31 +14,19 @@ import (
const pythonGrabber = `
try:
import sys
from youtube_dl import YoutubeDL
from youtube_dl.utils import DownloadError

if len(sys.argv) != 3:
sys.stderr.write('arguments: <format string> <cache dir>')
os.exit(1)

yt = YoutubeDL({
'geturl': True,
'format': sys.argv[1],
'cachedir': sys.argv[2] or None,
'quiet': True,
'simulate': True})
import pytube

while True:
stream = ''
try:
url = sys.stdin.readline().strip()
stream = yt.extract_info(url, ie_key='Youtube')['url']
stream = pytube.YouTube(str(url)).streams.first().url
except (KeyboardInterrupt, EOFError, IOError):
break
except DownloadError as why:
# error message has already been printed
sys.stderr.write('Could not extract video, try updating youtube-dl.\n')
finally:
except pytube.exceptions.ExtractError:
str = 'Could not extract video: ' + str(url) + '\n'
sys.stderr.write(str)
finally:
try:
sys.stdout.write(stream + '\n')
sys.stdout.flush()
Expand Down Expand Up @@ -74,17 +62,17 @@ func NewVideoGrabber() *VideoGrabber {
vg := VideoGrabber{}
vg.streams = make(map[string]*VideoURL)

cacheDir := *cacheDir
if cacheDir != "" {
cacheDir = cacheDir + "/" + "youtube-dl"
}
//cacheDir := *cacheDir
//if cacheDir != "" {
// cacheDir = cacheDir + "/" + "youtube-dl"
//}

// Start the process in a separate goroutine.
vg.cmdMutex.Lock()
go func() {
defer vg.cmdMutex.Unlock()

vg.cmd = exec.Command("python", "-c", pythonGrabber, grabberFormats, cacheDir)
vg.cmd = exec.Command("python3", "-c", pythonGrabber)//, grabberFormats, cacheDir)
stdout, err := vg.cmd.StdoutPipe()
if err != nil {
logger.Fatal(err)
Expand Down
10 changes: 9 additions & 1 deletion apps/youtube/youtube.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import (
"github.com/nu7hatch/gouuid"
)

var logger = log.New("youtube", "log YouTube app")
var logger = log.New("youtube", "Log YouTube app")

// How often a new connection attempt should be done.
// With a starting delay of 500ms that exponentially increases, this is about 5
Expand Down Expand Up @@ -108,6 +108,14 @@ func (yt *YouTube) FriendlyName() string {
return "YouTube"
}

func (yt *YouTube) Data(requestData string) string {
if requestData == "screenid" {
return yt.getScreenId()
}

return ""
}

// Start starts the YouTube app asynchronously.
// Attaches a new device if the app has already started.
func (yt *YouTube) Start(postData string) {
Expand Down
4 changes: 2 additions & 2 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ var configLock sync.Mutex

const CONFIG_FILENAME = ".config/plaincast.json"

var disableConfig = flag.Bool("no-config", false, "disable reading from and writing to config file")
var configPath = flag.String("config", "", "config file location (default "+CONFIG_FILENAME+")")
var disableConfig = flag.Bool("no-config", false, "Disable reading from and writing to config file")
var configPath = flag.String("config", "", "Config file location (default "+CONFIG_FILENAME+")")

// Get returns a global Config instance.
// It may be called multiple times: the same object will be returned each time.
Expand Down
2 changes: 1 addition & 1 deletion log/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const (

var isTerminal = terminal.IsTerminal(int(os.Stdout.Fd()))

var flagLoglevel = flag.String("loglevel", "warn", "baseline loglevel (info, warn, err)")
var flagLoglevel = flag.String("loglevel", "warn", "Baseline loglevel (info, warn, err)")

var loglevel = 0

Expand Down
2 changes: 2 additions & 0 deletions plaincast.service
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ After=network.target sound.target
ExecStart=/usr/local/bin/plaincast -log-mpv -log-youtube -config /var/local/plaincast/plaincast.conf -cachedir /var/local/plaincast/cache
User=plaincast
Group=audio
Restart=on-failure
RestartSec=2s

[Install]
WantedBy=multi-user.target
Loading