diff options
| author | Nathan Perry <avaglir@gmail.com> | 2017-07-28 23:15:43 -0400 |
|---|---|---|
| committer | Nathan Perry <avaglir@gmail.com> | 2017-07-28 23:15:43 -0400 |
| commit | 2d867f0e32ec8eacdd0c9bb51094cd38707e4d88 (patch) | |
| tree | d8a137166ed1e278aa4028092839b5cfcb5ac265 | |
| parent | 85d19683f6619150ff949822e1bb118472241d13 (diff) | |
just working with downloader
| -rw-r--r-- | downloader/download_manager.go | 85 | ||||
| -rw-r--r-- | downloader/downloader.go | 157 | ||||
| -rw-r--r-- | downloader/util.go | 77 | ||||
| -rw-r--r-- | wav/wav.go | 95 |
4 files changed, 260 insertions, 154 deletions
diff --git a/downloader/download_manager.go b/downloader/download_manager.go index 004efc1..de028ab 100644 --- a/downloader/download_manager.go +++ b/downloader/download_manager.go @@ -1,89 +1,14 @@ package downloader -import ( - "time" -) +import "github.com/bwmarrin/discordgo" -// DownloadManager handles a download for a particular song. type DownloadManager struct { - Url string - - Start time.Duration - Duration time.Duration - End time.Duration - - pb chan (<-chan []byte) - - info videoInfo + session *discordgo.Session } -const clipTime = 10 * time.Second -const preloadCount = 5 +func NewManager(s *discordgo.Session) *DownloadManager { -func NewDownload(url string, startTime, dur time.Duration) (*DownloadManager, error) { - vInfo, err := info(url) - if err != nil { - return nil, err + return &DownloadManager{ + session: s, } - - if dur == 0 { - dur = vInfo.Duration - startTime - } - - dl := &DownloadManager{ - Url: url, - - Start: startTime, - Duration: dur, - End: startTime + dur, - - pb: make(chan (<-chan []byte), preloadCount), - info: *vInfo, - } - - go dl.schedule() - - return dl, nil -} - -func (d *DownloadManager) SendOn(ch chan<- []byte) <-chan struct{} { - out := make(chan struct{}, 1) - - go func() { - defer close(out) - for c := range d.pb { - for b := range c { - ch <- b - } - } - }() - - return out -} - -func (d *DownloadManager) schedule() { - go func() { - defer close(d.pb) - for i := 0; ; i++ { - clipStart := time.Duration(i)*clipTime + d.Start - clipEnd := time.Duration(i+1)*clipTime + d.Start - - if clipStart >= d.End { - return - } - - dur := clipTime - if clipEnd > d.End { - dur = d.End - clipStart - } - - ch, err := d.download(clipStart, dur) - if err != nil { - log.Errorf("error setting up download: %q", err) - return - } - - d.pb <- ch - } - }() } diff --git a/downloader/downloader.go b/downloader/downloader.go index 95c022f..cab561b 100644 --- a/downloader/downloader.go +++ b/downloader/downloader.go @@ -1,81 +1,130 @@ package downloader import ( - "net/url" + "io/ioutil" + "os" "os/exec" "strconv" + "sync" "time" - "io/ioutil" - "os" - - "encoding/json" - "github.com/mammothbane/thulani-go/wav" - "github.com/op/go-logging" ) -var log = logging.MustGetLogger("downloader") +// Downloader handles a download for a particular song. +type Downloader struct { + Url string -// responsible for decoding from youtube -type videoInfo struct { - Title string `json:"fulltitle"` - UrlStr string `json:"url"` - DurationSec int `json:"duration"` - Url *url.URL `json:"-"` - Duration time.Duration `json:"-"` + Start time.Duration + Duration time.Duration + End time.Duration + + pause chan wav.State + once sync.Once + done chan struct{} + pb chan *wavBundle + + info videoInfo } -func info(inUrl string) (*videoInfo, error) { - dl := exec.Command("youtube-dl", "-f", "bestaudio", "-x", "-j", inUrl) +const clipTime = 10 * time.Second +const preloadCount = 5 - outpipe, err := dl.StdoutPipe() +func NewDownload(url string, startTime, dur time.Duration) (*Downloader, error) { + vInfo, err := info(url) if err != nil { return nil, err } - errpipe, err := dl.StderrPipe() - if err != nil { - return nil, err + if dur == 0 { + dur = vInfo.Duration - startTime } - err = dl.Start() - if err != nil { - log.Errorf("starting youtube-dl failed") - return nil, err - } + dl := &Downloader{ + Url: url, - o, ierr := ioutil.ReadAll(outpipe) - if ierr != nil { - log.Errorf("unable to read from output pipe") - return nil, err - } + Start: startTime, + Duration: dur, + End: startTime + dur, - e, ierr := ioutil.ReadAll(errpipe) - if ierr != nil { - log.Errorf("unable to read from error pipe") - return nil, err + pause: make(chan wav.State), + done: make(chan struct{}, 1), + pb: make(chan *wavBundle, preloadCount), + info: *vInfo, } - if err := dl.Wait(); err != nil { - log.Errorf("error:\n%v", string(e)) - return nil, err - } + go dl.schedule() - v := videoInfo{} - if err := json.Unmarshal(o, &v); err != nil { - return nil, err - } + return dl, nil +} - v.Duration = time.Duration(v.DurationSec) * time.Second - v.Url, err = url.Parse(v.UrlStr) +func (d *Downloader) Stop() { + d.once.Do(func() { + close(d.done) + }) +} + +func (d *Downloader) Resume() { + d.pause <- wav.Resume +} - //tgt, err := url.Parse(string(o)) - //out := tgt.Scheme + "://" + tgt.Host + tgt.Path + "?" + tgt.Query().Encode() - return &v, err +func (d *Downloader) Pause() { + d.pause <- wav.Pause } -func (d *DownloadManager) download(startTime, duration time.Duration) (<-chan []byte, error) { +func (d *Downloader) SendOn(ch chan<- []byte) <-chan struct{} { + out := make(chan struct{}, 1) + + go func() { + defer close(out) + for wavB := range d.pb { + wavB.wav.Start(ch) + + select { + case <-d.done: + wavB.wav.Stop() + wavB.cleanup() + + case <-wavB.wav.Done: + break + + case elem := <-d.pause: + wavB.wav.PlayState <- elem + } + } + }() + + return out +} + +func (d *Downloader) schedule() { + go func() { + defer close(d.pb) + for i := 0; ; i++ { + clipStart := time.Duration(i)*clipTime + d.Start + clipEnd := time.Duration(i+1)*clipTime + d.Start + + if clipStart >= d.End { + return + } + + dur := clipTime + if clipEnd > d.End { + dur = d.End - clipStart + } + + wavb, err := d.download(clipStart, dur) + if err != nil { + log.Errorf("error setting up download: %q", err) + return + } + + d.pb <- wavb + } + }() +} + +func (d *Downloader) download(startTime, duration time.Duration) (*wavBundle, error) { startSecond := int(startTime.Seconds()) args := []string{ "-ss", strconv.Itoa(startSecond), @@ -117,17 +166,11 @@ func (d *DownloadManager) download(startTime, duration time.Duration) (<-chan [] return nil, err } - ch := make(chan []byte, 1024*32) - done, err := wav.Load(file.Name(), ch) + wv, err := wav.New(file.Name()) if err != nil { clearTemp() return nil, err } - go func() { - <-done - clearTemp() - }() - - return ch, err + return &wavBundle{wav: wv, cleanup: clearTemp}, err } diff --git a/downloader/util.go b/downloader/util.go new file mode 100644 index 0000000..f744fad --- /dev/null +++ b/downloader/util.go @@ -0,0 +1,77 @@ +package downloader + +import ( + "encoding/json" + "io/ioutil" + "net/url" + "os/exec" + "time" + + "github.com/mammothbane/thulani-go/wav" + "github.com/op/go-logging" +) + +var log = logging.MustGetLogger("downloader") + +// responsible for decoding from youtube +type videoInfo struct { + Title string `json:"fulltitle"` + UrlStr string `json:"url"` + DurationSec int `json:"duration"` + Url *url.URL `json:"-"` + Duration time.Duration `json:"-"` +} + +func info(inUrl string) (*videoInfo, error) { + dl := exec.Command("youtube-dl", "-f", "bestaudio", "-x", "-j", inUrl) + + outpipe, err := dl.StdoutPipe() + if err != nil { + return nil, err + } + + errpipe, err := dl.StderrPipe() + if err != nil { + return nil, err + } + + err = dl.Start() + if err != nil { + log.Errorf("starting youtube-dl failed") + return nil, err + } + + o, ierr := ioutil.ReadAll(outpipe) + if ierr != nil { + log.Errorf("unable to read from output pipe") + return nil, err + } + + e, ierr := ioutil.ReadAll(errpipe) + if ierr != nil { + log.Errorf("unable to read from error pipe") + return nil, err + } + + if err := dl.Wait(); err != nil { + log.Errorf("error:\n%v", string(e)) + return nil, err + } + + v := videoInfo{} + if err := json.Unmarshal(o, &v); err != nil { + return nil, err + } + + v.Duration = time.Duration(v.DurationSec) * time.Second + v.Url, err = url.Parse(v.UrlStr) + + //tgt, err := url.Parse(string(o)) + //out := tgt.Scheme + "://" + tgt.Host + tgt.Path + "?" + tgt.Query().Encode() + return &v, err +} + +type wavBundle struct { + wav *wav.Wav + cleanup func() +} @@ -6,63 +6,126 @@ import "C" import ( "fmt" + "sync" + "github.com/op/go-logging" "layeh.com/gopus" ) +type State int + +const ( + Pause State = iota + Resume +) + // number of individual samples per channel per batch const samplesPerChannelPerBatch = 1920 var log = logging.MustGetLogger("wav") -func Load(filename string, ch chan<- []byte) (<-chan struct{}, error) { +type Wav struct { + filename *C.char + enc *gopus.Encoder + wav *C.drwav + + PlayState chan State + curState State + + once sync.Once + Done <-chan struct{} + done chan<- struct{} +} + +func New(filename string) (*Wav, error) { cfname := C.CString(filename) wav := C.drwav_open_file(cfname) if wav == nil { - close(ch) + C.drwav_close(wav) return nil, fmt.Errorf("Unable to initialize drwav.") } if int(wav.channels) != 2 { C.drwav_close(wav) - close(ch) return nil, fmt.Errorf("Wrong number of channels!") } if int(wav.sampleRate) != 48000 { C.drwav_close(wav) - close(ch) return nil, fmt.Errorf("Wrong sample rate.") } enc, err := gopus.NewEncoder(int(wav.sampleRate), int(wav.channels), gopus.Audio) if err != nil { - close(ch) + C.drwav_close(wav) return nil, err } done := make(chan struct{}, 1) - go func(wav *C.drwav) { - defer close(done) - defer close(ch) + return &Wav{ + filename: C.CString(filename), + enc: enc, + wav: wav, + + PlayState: make(chan State), + curState: Resume, - samplesPerBatch := samplesPerChannelPerBatch * int(wav.channels) - batchSize := samplesPerBatch * int(wav.bytesPerSample) + done: done, + Done: done, + }, nil +} + +func (w *Wav) Stop() { + w.once.Do(func() { + close(w.done) + }) +} + +func (w *Wav) Start(ch chan<- []byte) { + go func() { + defer w.Stop() + + samplesPerBatch := samplesPerChannelPerBatch * int(w.wav.channels) + batchSize := samplesPerBatch * int(w.wav.bytesPerSample) buf := C.malloc(C.size_t(batchSize)) defer C.free(buf) - defer C.drwav_close(wav) + defer C.drwav_close(w.wav) elems := make([]int16, samplesPerBatch) idx := 0 - for i := 0; i*samplesPerBatch <= int(wav.totalSampleCount); i += 1 { - readSamples := C.drwav_read_s16(wav, C.dr_uint64(samplesPerBatch), (*C.dr_int16)(buf)) + for i := 0; i*samplesPerBatch <= int(w.wav.totalSampleCount); i += 1 { + readSamples := C.drwav_read_s16(w.wav, C.dr_uint64(samplesPerBatch), (*C.dr_int16)(buf)) slc := (*[1 << 30]int16)(buf)[:readSamples:readSamples] readIdx := 0 + inner: for { + // check to see if we should die or pause + if w.curState == Pause { // if paused wait to die or get resumed + select { + case st := <-w.PlayState: + w.curState = st + continue inner + + case <-w.Done: + return + } + + } else { + select { + case st := <-w.PlayState: + w.curState = st + continue inner + + case <-w.Done: + return + default: + } + } + batchSamplesToFill := samplesPerBatch - idx readSamplesRemaining := int(readSamples) - readIdx @@ -75,7 +138,7 @@ func Load(filename string, ch chan<- []byte) (<-chan struct{}, error) { idx = 0 readIdx += batchSamplesToFill - b, err := processPCM(wav, enc, elems[:]) + b, err := processPCM(w.wav, w.enc, elems[:]) if err != nil { log.Errorf("error encoding pcm: %q", err) continue @@ -96,9 +159,7 @@ func Load(filename string, ch chan<- []byte) (<-chan struct{}, error) { break } } - }(wav) - - return done, nil + }() } func processPCM(wav *C.drwav, enc *gopus.Encoder, data []int16) ([]byte, error) { |
