From 34431c9fa55436f6886922e7e561628adb523057 Mon Sep 17 00:00:00 2001 From: Matthieu Grieger Date: Sat, 9 May 2015 21:45:00 -0700 Subject: [PATCH] https://github.com/matthieugrieger/mumbledj/issues/65: Add support for YouTube offsets --- commands.go | 25 +++++++++++++++---------- main.go | 8 ++------ service_youtube.go | 40 +++++++++++++++++++++++++++++++++++++--- songqueue.go | 8 +++++++- 4 files changed, 61 insertions(+), 20 deletions(-) diff --git a/commands.go b/commands.go index 3b5d928..6920092 100644 --- a/commands.go +++ b/commands.go @@ -164,27 +164,32 @@ func add(user *gumble.User, username, url string) { dj.SendPrivateMessage(user, NO_ARGUMENT_MSG) } else { youtubePatterns := []string{ - `https?:\/\/www\.youtube\.com\/watch\?v=([\w-]+)`, - `https?:\/\/youtube\.com\/watch\?v=([\w-]+)`, - `https?:\/\/youtu.be\/([\w-]+)`, - `https?:\/\/youtube.com\/v\/([\w-]+)`, - `https?:\/\/www.youtube.com\/v\/([\w-]+)`, + `https?:\/\/www\.youtube\.com\/watch\?v=([\w-]+)(\&t=\d*m?\d*s?)?`, + `https?:\/\/youtube\.com\/watch\?v=([\w-]+)(\&t=\d*m?\d*s?)?`, + `https?:\/\/youtu.be\/([\w-]+)(\?t=\d*m?\d*s?)?`, + `https?:\/\/youtube.com\/v\/([\w-]+)(\?t=\d*m?\d*s?)?`, + `https?:\/\/www.youtube.com\/v\/([\w-]+)(\?t=\d*m?\d*s?)?`, } matchFound := false - shortUrl := "" + shortURL := "" + startOffset := "" for _, pattern := range youtubePatterns { if re, err := regexp.Compile(pattern); err == nil { if re.MatchString(url) { matchFound = true - shortUrl = re.FindStringSubmatch(url)[1] + matches := re.FindAllStringSubmatch(url, -1) + shortURL = matches[0][1] + if len(matches[0]) == 3 { + startOffset = matches[0][2] + } break } } } if matchFound { - if newSong, err := NewYouTubeSong(username, shortUrl, nil); err == nil { + if newSong, err := NewYouTubeSong(username, shortURL, startOffset, nil); err == nil { dj.client.Self.Channel.Send(fmt.Sprintf(SONG_ADDED_HTML, username, newSong.title), false) if dj.queue.Len() == 1 && !dj.audioStream.IsPlaying() { if err := dj.queue.CurrentSong().Download(); err == nil { @@ -206,9 +211,9 @@ func add(user *gumble.User, username, url string) { if re, err := regexp.Compile(youtubePlaylistPattern); err == nil { if re.MatchString(url) { if dj.HasPermission(username, dj.conf.Permissions.AdminAddPlaylists) { - shortUrl = re.FindStringSubmatch(url)[1] + shortURL = re.FindStringSubmatch(url)[1] oldLength := dj.queue.Len() - if newPlaylist, err := NewYouTubePlaylist(username, shortUrl); err == nil { + if newPlaylist, err := NewYouTubePlaylist(username, shortURL); err == nil { dj.client.Self.Channel.Send(fmt.Sprintf(PLAYLIST_ADDED_HTML, username, newPlaylist.title), false) if oldLength == 0 && dj.queue.Len() != 0 && !dj.audioStream.IsPlaying() { if err := dj.queue.CurrentSong().Download(); err == nil { diff --git a/main.go b/main.go index 871ea4c..e0f786a 100644 --- a/main.go +++ b/main.go @@ -45,12 +45,8 @@ func (dj *mumbledj) OnConnect(e *gumble.ConnectEvent) { fmt.Println("Channel doesn't exist or one was not provided, staying in root channel...") } - if audioStream, err := gumble_ffmpeg.New(dj.client); err == nil { - dj.audioStream = audioStream - dj.audioStream.Volume = dj.conf.Volume.DefaultVolume - } else { - panic(err) - } + dj.audioStream = gumble_ffmpeg.New(dj.client) + dj.audioStream.Volume = dj.conf.Volume.DefaultVolume dj.client.AudioEncoder.SetApplication(gopus.Audio) diff --git a/service_youtube.go b/service_youtube.go index a9ad33e..b91827a 100644 --- a/service_youtube.go +++ b/service_youtube.go @@ -17,8 +17,10 @@ import ( "os/exec" "strconv" "strings" + "time" "github.com/jmoiron/jsonq" + "github.com/layeh/gumble/gumble_ffmpeg" ) // ------------ @@ -30,6 +32,7 @@ type YouTubeSong struct { submitter string title string id string + offset int filename string duration string thumbnail string @@ -40,7 +43,7 @@ type YouTubeSong struct { // NewYouTubeSong gathers the metadata for a song extracted from a YouTube video, and returns // the song. -func NewYouTubeSong(user, id string, playlist *YouTubePlaylist) (*YouTubeSong, error) { +func NewYouTubeSong(user, id, offset string, playlist *YouTubePlaylist) (*YouTubeSong, error) { var apiResponse *jsonq.JsonQuery var err error url := fmt.Sprintf("https://www.googleapis.com/youtube/v3/videos?part=snippet,contentDetails&id=%s&key=%s", @@ -49,6 +52,24 @@ func NewYouTubeSong(user, id string, playlist *YouTubePlaylist) (*YouTubeSong, e return nil, err } + var offsetMinutes, offsetSeconds int64 + if offset != "" { + if strings.Contains(offset, "m") { + offsetMinutes, _ = strconv.ParseInt(offset[3:strings.Index(offset, "m")], 10, 32) + if strings.Contains(offset, "s") { + offsetSeconds, _ = strconv.ParseInt(offset[strings.Index(offset, "m")+1:strings.Index(offset, "s")], 10, 32) + } else { + offsetSeconds = 0 + } + } else if strings.Contains(offset, "s") { + offsetMinutes = 0 + offsetSeconds, _ = strconv.ParseInt(offset[3:strings.Index(offset, "s")], 10, 32) + } + } else { + offsetMinutes = 0 + offsetSeconds = 0 + } + title, _ := apiResponse.String("items", "0", "snippet", "title") thumbnail, _ := apiResponse.String("items", "0", "snippet", "thumbnails", "high", "url") duration, _ := apiResponse.String("items", "0", "contentDetails", "duration") @@ -64,14 +85,18 @@ func NewYouTubeSong(user, id string, playlist *YouTubePlaylist) (*YouTubeSong, e } else { seconds = 0 } + + combinedMinutes := minutes - offsetMinutes + combinedSeconds := seconds - offsetSeconds totalSeconds := int((minutes * 60) + seconds) - durationString := fmt.Sprintf("%d:%02d", minutes, seconds) + durationString := fmt.Sprintf("%d:%02d", combinedMinutes, combinedSeconds) if dj.conf.General.MaxSongDuration == 0 || totalSeconds <= dj.conf.General.MaxSongDuration { song := &YouTubeSong{ submitter: user, title: title, id: id, + offset: int((offsetMinutes * 60) + offsetSeconds), filename: id + ".m4a", duration: durationString, thumbnail: thumbnail, @@ -104,7 +129,12 @@ func (s *YouTubeSong) Download() error { // Play plays the song. Once the song is playing, a notification is displayed in a text message that features the video // thumbnail, URL, title, duration, and submitter. func (s *YouTubeSong) Play() { - if err := dj.audioStream.Play(fmt.Sprintf("%s/.mumbledj/songs/%s.m4a", dj.homeDir, s.ID()), dj.queue.OnSongFinished); err != nil { + if s.offset != 0 { + offsetDuration, _ := time.ParseDuration(fmt.Sprintf("%ds", s.offset)) + dj.audioStream.Offset = offsetDuration + } + dj.audioStream.Source = gumble_ffmpeg.SourceFile(fmt.Sprintf("%s/.mumbledj/songs/%s", dj.homeDir, s.Filename())) + if err := dj.audioStream.Play(); err != nil { panic(err) } else { if s.Playlist() == nil { @@ -143,6 +173,10 @@ func (s *YouTubeSong) Play() { dj.client.Self.Channel.Send(fmt.Sprintf(message, s.Thumbnail(), s.ID(), s.Title(), s.Duration(), s.Submitter(), s.Playlist().Title()), false) } + go func() { + dj.audioStream.Wait() + dj.queue.OnSongFinished() + }() } } diff --git a/songqueue.go b/songqueue.go index e157c54..b9d20f8 100644 --- a/songqueue.go +++ b/songqueue.go @@ -7,7 +7,11 @@ package main -import "errors" +import ( + "errors" + "fmt" + "time" +) // SongQueue type declaration. type SongQueue struct { @@ -75,6 +79,8 @@ func (q *SongQueue) Traverse(visit func(i int, s Song)) { // OnSongFinished event. Deletes Song that just finished playing, then queues the next Song (if exists). func (q *SongQueue) OnSongFinished() { + resetOffset, _ := time.ParseDuration(fmt.Sprintf("%ds", 0)) + dj.audioStream.Offset = resetOffset if q.Len() != 0 { if dj.queue.CurrentSong().DontSkip() == true { dj.queue.CurrentSong().SetDontSkip(false)