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