From e8a71b25302d21bd88dae9972484c7acc8ceb82a Mon Sep 17 00:00:00 2001 From: Who23 <40632266+Who23@users.noreply.github.com> Date: Fri, 4 Sep 2020 20:49:07 -0400 Subject: [PATCH 1/2] Add ability to specifiy youtube URL source Added a new flag (-u) to specify a youtube URL source when downloading a single song. --- README.md | 1 + src/bottle/cli.cr | 4 +++- src/glue/song.cr | 30 +++++++++++++++++++++--------- src/search/youtube.cr | 11 +++++++++++ 4 files changed, 36 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 9c763bc..9ab8d77 100755 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ Arguments: -s, --song Specify song name to download -A, --album Specify the album name to download -p, --playlist Specify the playlist name to download + -u, --url Specify the youtube url to download from Examples: $ irs --song "Bohemian Rhapsody" --artist "Queen" diff --git a/src/bottle/cli.cr b/src/bottle/cli.cr index 2cbf378..542d510 100755 --- a/src/bottle/cli.cr +++ b/src/bottle/cli.cr @@ -20,6 +20,7 @@ class CLI [["-s", "--song"], "song", "string"], [["-A", "--album"], "album", "string"], [["-p", "--playlist"], "playlist", "string"], + [["-u", "--url"], "url", "string"], ] @args : Hash(String, String) @@ -48,6 +49,7 @@ class CLI #{Style.blue "-s, --song "} Specify song name to download #{Style.blue "-A, --album "} Specify the album name to download #{Style.blue "-p, --playlist "} Specify the playlist name to download + #{Style.blue "-u, --url "} Specify the youtube url to download from (for single songs only) #{Style.bold "Examples:"} $ #{Style.green %(irs --song "Bohemian Rhapsody" --artist "Queen")} @@ -82,7 +84,7 @@ class CLI elsif @args["song"]? && @args["artist"]? s = Song.new(@args["song"], @args["artist"]) s.provide_client_keys(Config.client_key, Config.client_secret) - s.grab_it + s.grab_it(@args["url"]?) s.organize_it(Config.music_directory) exit elsif @args["album"]? && @args["artist"]? diff --git a/src/glue/song.cr b/src/glue/song.cr index 2cc175a..dba2996 100755 --- a/src/glue/song.cr +++ b/src/glue/song.cr @@ -24,7 +24,9 @@ class Song ], "url" => [ " Searching for URL ...\r", - Style.green(" + ") + Style.dim("URL found \n") + Style.green(" + ") + Style.dim("URL found \n"), + " Validating URL ...\r", + Style.green(" + ") + Style.dim("URL validated \n") ], "download" => [ " Downloading video:\n", @@ -47,11 +49,12 @@ class Song end # Find, downloads, and tags the mp3 song that this class represents. - # + # Optionally takes a youtube URL to download from + # # ``` # Song.new("Bohemian Rhapsody", "Queen").grab_it # ``` - def grab_it + def grab_it(url : (String | Nil) = nil) outputter("intro", 0) if !@spotify_searcher.authorized? && !@metadata @@ -81,14 +84,23 @@ class Song data = @metadata.as(JSON::Any) @filename = data["track_number"].to_s + " - #{data["name"].to_s}.mp3" - outputter("url", 0) - url = Youtube.find_url(@song_name, @artist_name, search_terms: "lyrics") if !url - raise("There was no url found on youtube for " + - %("#{@song_name}" by "#{@artist_name}. ) + - "Check your input and try again.") + outputter("url", 0) + url = Youtube.find_url(@song_name, @artist_name, search_terms: "lyrics") + if !url + raise("There was no url found on youtube for " + + %("#{@song_name}" by "#{@artist_name}. ) + + "Check your input and try again.") + end + outputter("url", 1) + else + outputter("url", 2) + if !Youtube.is_valid_url(url) + raise("The url '#{url}' is an invalid youtube URL " + + "Check the URL and try again") + end + outputter("url", 3) end - outputter("url", 1) outputter("download", 0) Ripper.download_mp3(url.as(String), @filename) diff --git a/src/search/youtube.cr b/src/search/youtube.cr index 7242ea3..635286a 100755 --- a/src/search/youtube.cr +++ b/src/search/youtube.cr @@ -23,6 +23,17 @@ module Youtube alias NODES_CLASS = Array(Hash(String, String)) + # Checks if the given URL is a valid youtube URL + # + # ``` + # Youtube.is_valid_url("https://www.youtube.com/watch?v=NOTANACTUALVIDEOID") + # => false + # ``` + def is_valid_url(url : String) : Bool + response = HTTP::Client.get(url) + return !(response.status_code == 404) + end + # Finds a youtube url based off of the given information. # The query to youtube is constructed like this: # " " From dd8c74520c1cff195d4b4e7019c20a4aa51016ba Mon Sep 17 00:00:00 2001 From: Who23 <40632266+Who23@users.noreply.github.com> Date: Mon, 7 Sep 2020 18:16:31 -0400 Subject: [PATCH 2/2] URL source for albums/playlists & Youtube module improvements - Add ability to source youtube URls for albums and playlists, through the -g flag, which prompts for user input on each song - Fix the Youtube.is_valid_url function, which now actually checks whether the given URL points to an actual video --- README.md | 3 ++- src/bottle/cli.cr | 6 ++++-- src/glue/list.cr | 11 +++++++++-- src/glue/song.cr | 13 +++++++++++-- src/search/youtube.cr | 33 +++++++++++++++++++++++++++++++-- 5 files changed, 57 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 9ab8d77..f77811a 100755 --- a/README.md +++ b/README.md @@ -54,7 +54,8 @@ Arguments: -s, --song Specify song name to download -A, --album Specify the album name to download -p, --playlist Specify the playlist name to download - -u, --url Specify the youtube url to download from + -u, --url Specify the youtube url to download from (for single songs only) + -g, --give-url Specify the youtube url sources while downloading (for albums or playlists only only) Examples: $ irs --song "Bohemian Rhapsody" --artist "Queen" diff --git a/src/bottle/cli.cr b/src/bottle/cli.cr index 542d510..b6b74ae 100755 --- a/src/bottle/cli.cr +++ b/src/bottle/cli.cr @@ -21,6 +21,7 @@ class CLI [["-A", "--album"], "album", "string"], [["-p", "--playlist"], "playlist", "string"], [["-u", "--url"], "url", "string"], + [["-g", "--give-url"], "give-url", "bool"], ] @args : Hash(String, String) @@ -50,6 +51,7 @@ class CLI #{Style.blue "-A, --album "} Specify the album name to download #{Style.blue "-p, --playlist "} Specify the playlist name to download #{Style.blue "-u, --url "} Specify the youtube url to download from (for single songs only) + #{Style.blue "-g, --give-url"} Specify the youtube url sources while downloading (for albums or playlists only) #{Style.bold "Examples:"} $ #{Style.green %(irs --song "Bohemian Rhapsody" --artist "Queen")} @@ -90,11 +92,11 @@ class CLI elsif @args["album"]? && @args["artist"]? a = Album.new(@args["album"], @args["artist"]) a.provide_client_keys(Config.client_key, Config.client_secret) - a.grab_it + a.grab_it(!!@args["give-url"]) elsif @args["playlist"]? && @args["artist"]? p = Playlist.new(@args["playlist"], @args["artist"]) p.provide_client_keys(Config.client_key, Config.client_secret) - p.grab_it + p.grab_it(!!@args["give-url"]) else puts Style.red("Those arguments don't do anything when used that way.") puts "Type `irs -h` to see usage." diff --git a/src/glue/list.cr b/src/glue/list.cr index 4d4d583..99c7134 100755 --- a/src/glue/list.cr +++ b/src/glue/list.cr @@ -17,6 +17,9 @@ abstract class SpotifyList "searching" => [ Style.bold("Searching for %l by %a ... \r"), Style.green("+ ") + Style.bold("%l by %a \n") + ], + "url" => [ + Style.bold("When prompted for a URL, provide a youtube URL or press enter to scrape for one\n") ] } @@ -24,11 +27,15 @@ abstract class SpotifyList end # Finds the list, and downloads all of the songs using the `Song` class - def grab_it + def grab_it(ask_url : Bool = false) if !@spotify_searcher.authorized? raise("Need to call provide_client_keys on Album or Playlist class.") end + if ask_url + outputter("url", 0) + end + outputter("searching", 0) list = find_it() outputter("searching", 1) @@ -47,7 +54,7 @@ abstract class SpotifyList song.provide_metadata(data) puts Style.bold("[#{data["track_number"]}/#{contents.size}]") - song.grab_it + song.grab_it ask_url: ask_url organize(song) diff --git a/src/glue/song.cr b/src/glue/song.cr index dba2996..b8b2e1b 100755 --- a/src/glue/song.cr +++ b/src/glue/song.cr @@ -26,7 +26,8 @@ class Song " Searching for URL ...\r", Style.green(" + ") + Style.dim("URL found \n"), " Validating URL ...\r", - Style.green(" + ") + Style.dim("URL validated \n") + Style.green(" + ") + Style.dim("URL validated \n"), + "URL?: " ], "download" => [ " Downloading video:\n", @@ -54,7 +55,7 @@ class Song # ``` # Song.new("Bohemian Rhapsody", "Queen").grab_it # ``` - def grab_it(url : (String | Nil) = nil) + def grab_it(url : (String | Nil) = nil, ask_url : Bool = false) outputter("intro", 0) if !@spotify_searcher.authorized? && !@metadata @@ -84,6 +85,14 @@ class Song data = @metadata.as(JSON::Any) @filename = data["track_number"].to_s + " - #{data["name"].to_s}.mp3" + if ask_url + outputter("url", 4) + url = gets + if !url.nil? && url.strip == "" + url = nil + end + end + if !url outputter("url", 0) url = Youtube.find_url(@song_name, @artist_name, search_terms: "lyrics") diff --git a/src/search/youtube.cr b/src/search/youtube.cr index 635286a..de5a8b1 100755 --- a/src/search/youtube.cr +++ b/src/search/youtube.cr @@ -1,6 +1,7 @@ require "http" require "xml" require "json" +require "uri" module Youtube @@ -30,8 +31,36 @@ module Youtube # => false # ``` def is_valid_url(url : String) : Bool - response = HTTP::Client.get(url) - return !(response.status_code == 404) + uri = URI.parse url + + # is it a video on youtube, with a query + query = uri.query + if uri.host != "www.youtube.com" || uri.path != "/watch" || !query + return false + end + + + queries = query.split('&') + + # find the video ID + i = 0 + while i < queries.size + if queries[i].starts_with?("v=") + vID = queries[i][2..-1] + break + end + i += 1 + end + + if !vID + return false + end + + + # this is an internal endpoint to validate the video ID + response = HTTP::Client.get "https://www.youtube.com/get_video_info?video_id=#{vID}" + + return response.body.includes?("status=ok") end # Finds a youtube url based off of the given information.