add code
This commit is contained in:
26
ytdl/error.go
Normal file
26
ytdl/error.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package ytdl
|
||||
|
||||
type Error struct {
|
||||
// `Input` is the input given to youtube-dl when the error occurred.
|
||||
// If `Input` is set to "", it is ignored.
|
||||
Input string
|
||||
// `Err` is the underlying error.
|
||||
Err error
|
||||
}
|
||||
|
||||
// `input` may be set to "", in which case it is ignored when outputting the
|
||||
// error message.
|
||||
func newError(input string, err error) *Error {
|
||||
return &Error{
|
||||
Input: input,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
if e.Input == "" {
|
||||
return "ytdl: " + e.Err.Error()
|
||||
} else {
|
||||
return "ytdl['" + e.Input + "']: " + e.Err.Error()
|
||||
}
|
||||
}
|
||||
165
ytdl/extractor.go
Normal file
165
ytdl/extractor.go
Normal file
@@ -0,0 +1,165 @@
|
||||
package ytdl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
type Extractor struct {
|
||||
DefaultSearch string
|
||||
YtdlPath string
|
||||
}
|
||||
|
||||
func NewExtractor(ytdlPath string) *Extractor {
|
||||
return &Extractor{
|
||||
DefaultSearch: "ytsearch",
|
||||
YtdlPath: ytdlPath,
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
errInvalidMetadata = errors.New("invalid metadata received from youtube-dl")
|
||||
)
|
||||
|
||||
type Metadata map[string]interface{}
|
||||
|
||||
/*// `input` can be a URL or a search query.
|
||||
// Returns a slice with size 1 if the input is a single media file. Returns a
|
||||
// larger slice if the input is a playlist.
|
||||
// Progress sends a struct{} every time a single item has been added.
|
||||
func (e *Extractor) GetMetadata(input string, progress chan<- struct{}) ([]Metadata, error) {
|
||||
cmd := exec.Command(e.YtdlPath, "--default-search", e.DefaultSearch, "-j", input)
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
if err := cmd.Start(); err != nil {
|
||||
return nil, newError(input, err)
|
||||
}
|
||||
|
||||
defer close(progress)
|
||||
|
||||
var m sync.Mutex
|
||||
var done bool
|
||||
var ret []Metadata
|
||||
var ytdlErr error
|
||||
var killedYtdl bool
|
||||
go func() {
|
||||
killYtdl := func(err error) {
|
||||
m.Lock()
|
||||
ytdlErr = err
|
||||
cmd.Process.Signal(os.Interrupt)
|
||||
killedYtdl = true
|
||||
m.Unlock()
|
||||
}
|
||||
|
||||
dec := json.NewDecoder(&out)
|
||||
m.Lock()
|
||||
d := done
|
||||
m.Unlock()
|
||||
for !d && dec.More() {
|
||||
var meta interface{}
|
||||
if err := dec.Decode(&meta); err != nil {
|
||||
killYtdl(newError(input, err))
|
||||
}
|
||||
progress <- struct{}{}
|
||||
|
||||
if m, ok := meta.(map[string]interface{}); ok {
|
||||
ret = append(ret, m)
|
||||
} else {
|
||||
killYtdl(newError(input, errInvalidMetadata))
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
err := cmd.Wait()
|
||||
m.Lock()
|
||||
done = true
|
||||
m.Unlock()
|
||||
if ytdlErr != nil {
|
||||
return nil, ytdlErr
|
||||
}
|
||||
if err != nil && !killedYtdl {
|
||||
return nil, newError(input, err)
|
||||
}
|
||||
return ret, nil
|
||||
}*/
|
||||
|
||||
|
||||
// `input` can be a URL or a search query.
|
||||
// Returns a slice with size 1 if the input is a single media file. Returns a
|
||||
// larger slice if the input is a playlist.
|
||||
func (e *Extractor) GetMetadata(input string) ([]Metadata, error) {
|
||||
cmd := exec.Command(e.YtdlPath, "--default-search", e.DefaultSearch, "-j", input)
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
if err := cmd.Run(); err != nil {
|
||||
return nil, newError(input, err)
|
||||
}
|
||||
|
||||
var ret []Metadata
|
||||
dec := json.NewDecoder(&out)
|
||||
for dec.More() {
|
||||
var meta interface{}
|
||||
if err := dec.Decode(&meta); err != nil {
|
||||
return nil, newError(input, err)
|
||||
}
|
||||
|
||||
if m, ok := meta.(map[string]interface{}); ok {
|
||||
ret = append(ret, m)
|
||||
} else {
|
||||
return nil, newError(input, errInvalidMetadata)
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// Returns the best available audio-only format. If the given URL links directly
|
||||
// to a media file, it just returns that URL.
|
||||
func GetAudioURL(meta Metadata) (string, error) {
|
||||
// This function has a lot of code, but what it does is actually pretty
|
||||
// simple. We just have to do a lot to ensure that there are no integrity
|
||||
// issues with the metadata.
|
||||
// 'iSomething' stands for 'interface something' here.
|
||||
|
||||
iExtractor, ok := meta["extractor"]
|
||||
if !ok {
|
||||
// Extractor not specified.
|
||||
return "", newError("", errInvalidMetadata)
|
||||
}
|
||||
if extractor, ok := iExtractor.(string); ok {
|
||||
if extractor == "generic" {
|
||||
url, ok := meta["url"]
|
||||
if u := url.(string); ok {
|
||||
return u, nil
|
||||
} else {
|
||||
return "", newError("", errors.New("unable to get any audio or video URL"))
|
||||
}
|
||||
}
|
||||
|
||||
iFormats, ok := meta["formats"]
|
||||
if !ok {
|
||||
return "", newError("", errors.New("no format selection available and no raw URL specified"))
|
||||
}
|
||||
// Get the best audio format.
|
||||
if formats, ok := iFormats.([]interface{}); ok {
|
||||
// In youtube-dl, the last format is always the best one. Here we're
|
||||
// looking for the last (=best) format that contains no video.
|
||||
for i := len(formats) - 1; i >= 0; i-- {
|
||||
iFormat := formats[i]
|
||||
format, ok := iFormat.(map[string]interface{})
|
||||
if !ok {
|
||||
return "", newError("", errInvalidMetadata)
|
||||
}
|
||||
if format["vcodec"].(string) == "none" {
|
||||
return format["url"].(string), nil
|
||||
}
|
||||
}
|
||||
return "", newError("", errors.New("unable to find any audio-only format"))
|
||||
} else {
|
||||
return "", newError("", errInvalidMetadata)
|
||||
}
|
||||
} else {
|
||||
return "", newError("", errInvalidMetadata)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user