init
This commit is contained in:
35
extractor/ytdl/providers.go
Normal file
35
extractor/ytdl/providers.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package ytdl
|
||||
|
||||
import (
|
||||
"git.nobrain.org/r4/dischord/extractor"
|
||||
|
||||
"strings"
|
||||
)
|
||||
|
||||
func init() {
|
||||
extractor.AddExtractor("youtube-dl", &Extractor{})
|
||||
}
|
||||
|
||||
type Extractor struct{}
|
||||
|
||||
func (e *Extractor) DefaultConfig() extractor.ProviderConfig {
|
||||
return extractor.ProviderConfig{
|
||||
"youtube-dl-path": "youtube-dl",
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Extractor) Matches(cfg extractor.ProviderConfig, input string) bool {
|
||||
return strings.HasPrefix(input, "http://") || strings.HasPrefix(input, "https://")
|
||||
}
|
||||
|
||||
func (e *Extractor) Extract(cfg extractor.ProviderConfig, input string) ([]extractor.Data, error) {
|
||||
var res []extractor.Data
|
||||
dch, errch := ytdlGet(cfg["youtube-dl-path"].(string), input)
|
||||
for v := range dch {
|
||||
res = append(res, v)
|
||||
}
|
||||
for err := range errch {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
135
extractor/ytdl/ytdl.go
Normal file
135
extractor/ytdl/ytdl.go
Normal file
@@ -0,0 +1,135 @@
|
||||
package ytdl
|
||||
|
||||
import (
|
||||
"git.nobrain.org/r4/dischord/extractor"
|
||||
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrUnsupportedUrl = errors.New("unsupported URL")
|
||||
)
|
||||
|
||||
// A very reduced version of the JSON structure returned by youtube-dl
|
||||
type ytdlMetadata struct {
|
||||
Title string `json:"title"`
|
||||
Extractor string `json:"extractor"`
|
||||
Duration float32 `json:"duration"`
|
||||
WebpageUrl string `json:"webpage_url"`
|
||||
Playlist string `json:"playlist"`
|
||||
Uploader string `json:"uploader"`
|
||||
Description string `json:"description"`
|
||||
Formats []struct {
|
||||
Url string `json:"url"`
|
||||
Format string `json"format"`
|
||||
VCodec string `json:"vcodec"`
|
||||
} `json:"formats"`
|
||||
}
|
||||
|
||||
// Gradually sends all audio URLs through the string channel. If an error occurs, it is sent through the
|
||||
// error channel. Both channels are closed after either an error occurs or all URLs have been output.
|
||||
func ytdlGet(youtubeDLPath, input string) (<-chan extractor.Data, <-chan error) {
|
||||
out := make(chan extractor.Data)
|
||||
errch := make(chan error, 1)
|
||||
|
||||
go func() {
|
||||
defer close(out)
|
||||
defer close(errch)
|
||||
|
||||
// Set youtube-dl args
|
||||
var ytdlArgs []string
|
||||
ytdlArgs = append(ytdlArgs, "-j", input)
|
||||
|
||||
// Prepare command for execution
|
||||
cmd := exec.Command(youtubeDLPath, ytdlArgs...)
|
||||
cmd.Env = []string{"LC_ALL=en_US.UTF-8"} // Youtube-dl doesn't recognize some chars if LC_ALL=C or not set at all
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
errch <- err
|
||||
return
|
||||
}
|
||||
stderr, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
errch <- err
|
||||
return
|
||||
}
|
||||
|
||||
// Catch any errors put out by youtube-dl
|
||||
stderrReadDoneCh := make(chan struct{})
|
||||
var ytdlError string
|
||||
go func() {
|
||||
sc := bufio.NewScanner(stderr)
|
||||
for sc.Scan() {
|
||||
line := sc.Text()
|
||||
if strings.HasPrefix(line, "ERROR: ") {
|
||||
ytdlError = strings.TrimPrefix(line, "ERROR: ")
|
||||
}
|
||||
}
|
||||
stderrReadDoneCh <- struct{}{}
|
||||
}()
|
||||
|
||||
// Start youtube-dl
|
||||
if err := cmd.Start(); err != nil {
|
||||
errch <- err
|
||||
return
|
||||
}
|
||||
|
||||
// We want to let our main loop know when youtube-dl is done
|
||||
donech := make(chan error)
|
||||
go func() {
|
||||
donech <- cmd.Wait()
|
||||
}()
|
||||
|
||||
// Main JSON decoder loop
|
||||
dec := json.NewDecoder(stdout)
|
||||
for dec.More() {
|
||||
// Read JSON
|
||||
var m ytdlMetadata
|
||||
if err := dec.Decode(&m); err != nil {
|
||||
errch <- err
|
||||
return
|
||||
}
|
||||
|
||||
// Extract URL from metadata (the latter formats are always the better with youtube-dl)
|
||||
for i := len(m.Formats) - 1; i >= 0; i-- {
|
||||
format := m.Formats[i]
|
||||
if format.VCodec == "none" {
|
||||
out <- extractor.Data{
|
||||
SourceUrl: m.WebpageUrl,
|
||||
StreamUrl: format.Url,
|
||||
Title: m.Title,
|
||||
PlaylistTitle: m.Playlist,
|
||||
Description: m.Description,
|
||||
Uploader: m.Uploader,
|
||||
Duration: int(m.Duration),
|
||||
Expires: time.Now().Add(10 * 365 * 24 * time.Hour),
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for command to finish executing and catch any errors
|
||||
err = <-donech
|
||||
<-stderrReadDoneCh
|
||||
if err != nil {
|
||||
if ytdlError == "" {
|
||||
errch <- err
|
||||
} else {
|
||||
if strings.HasPrefix(ytdlError, "Unsupported URL: ") {
|
||||
errch <- ErrUnsupportedUrl
|
||||
} else {
|
||||
errch <- errors.New("ytdl: " + ytdlError)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
return out, errch
|
||||
}
|
||||
Reference in New Issue
Block a user