song glue done!

This commit is contained in:
Cooper Hammond 2019-06-19 19:06:09 -07:00
parent f8dc95265f
commit acd2abb1d0
6 changed files with 184 additions and 25 deletions

2
.gitignore vendored
View file

@ -3,3 +3,5 @@
/bin/
/.shards/
*.dwarf
*.mp3

122
src/glue/song.cr Normal file
View file

@ -0,0 +1,122 @@
require "../search/spotify"
require "../search/youtube"
require "../interact/ripper"
require "../interact/tagger"
class Song
@spotify_searcher = SpotifySearcher.new()
@client_id = ""
@client_secret = ""
@metadata : JSON::Any?
@filename : String?
def initialize(@song_name : String, @artist_name : String)
end
# Find, downloads, and tags the mp3 song that this class represents.
# Will return true on complete success and false on failure.
#
# ```
# Song.new("Bohemian Rhapsody", "Queen").grab_it()
# ```
def grab_it : Nil
if !@spotify_searcher.authorized? && !@metadata
if @client_id != "" && @client_secret != ""
@spotify_searcher.authorize(@client_id, @client_secret)
else
raise("Need to call either `provide_metadata`, `provide_spotify`, " +
"or `provide_client_keys` so that Spotify can be interfaced with.")
end
end
if !@metadata
@metadata = @spotify_searcher.find_item("track", {
"name" => @song_name,
"artist" => @artist_name
})
if !@metadata
raise("There was no metadata found on Spotify for\n" +
%("#{@song_name}" by "#{@artist_name}\n) +
"Check your input and try again.")
end
end
data = @metadata.as(JSON::Any)
filename = data["track_number"].to_s + " - #{data["name"].to_s}.mp3"
url = Youtube.find_url(@song_name, @artist_name, search_terms: "lyrics")
if !url
raise("There was no link found on youtube for\n" +
%("#{@song_name}" by "#{@artist_name}\n) +
"Check your input and try again.")
end
Ripper.download_mp3(url.as(String), filename)
temp_albumart_filename = ".tempalbumart.jpg"
HTTP::Client.get(data["album"]["images"][0]["url"].to_s) do |response|
File.write(temp_albumart_filename, response.body_io)
end
tagger = Tags.new(filename)
tagger.add_album_art(temp_albumart_filename)
tagger.add_text_tag("title", data["name"].to_s)
tagger.add_text_tag("artist", data["artists"][0]["name"].to_s)
tagger.add_text_tag("album", data["album"]["name"].to_s)
tagger.add_text_tag("genre",
@spotify_searcher.find_genre(data["artists"][0]["id"].to_s))
tagger.add_text_tag("track", data["track_number"].to_s)
tagger.add_text_tag("disc", data["disc_number"].to_s)
tagger.save()
File.delete(temp_albumart_filename)
end
# Provide metadata so that it doesn't have to find it. Useful for overwriting
# metadata. Must be called if provide_client_keys and provide_spotify are not
# called.
#
# ```
# Song.new(...).provide_metadata(...).grab_it()
# ```
def provide_metadata(metadata : JSON::Any) : self
@metadata = metadata
return self
end
# Provide an already authenticated `SpotifySearcher` class. Useful to avoid
# authenticating over and over again. Must be called if provide_metadata and
# provide_client_keys are not called.
#
# ```
# Song.new(...).provide_spotify(SpotifySearcher.new()
# .authenticate("XXXXXXXXXX", "XXXXXXXXXXX")).grab_it()
# ```
def provide_spotify(spotify : SpotifySearcher) : self
@spotify = spotify
return self
end
# Provide spotify client keys. Must be called if provide_metadata and
# provide_spotify are not called.
#
# ```
# Song.new(...).provide_client_keys("XXXXXXXXXX", "XXXXXXXXX").grab_it()
# ```
def provide_client_keys(client_id : String, client_secret : String) : self
@client_id = client_id
@client_secret = client_secret
return self
end
end
# s = Song.new("Bohemian Rhapsody", "Queen")
# s.provide_client_keys("e4198f6a3f7b48029366f22528b5dc66", "ba057d0621a5496bbb64edccf758bde5")
# s.grab_it()

View file

@ -11,7 +11,7 @@ module Ripper
# Ripper.download_mp3("https://youtube.com/watch?v=0xnciFWAqa0",
# "Queen/A Night At The Opera/Bohemian Rhapsody.mp3")
# ```
def download_mp3(video_url : String, output_filename : String) : Nil
def download_mp3(video_url : String, output_filename : String) : Bool
ydl_loc = BIN_LOC.join("youtube-dl")
# remove the extension that will be added on by ydl
@ -33,7 +33,11 @@ module Ripper
command += " #{option} #{options[option]}"
end
system(command)
if system(command)
return true
else
return false
end
end
end

View file

@ -36,7 +36,7 @@ class Tags
def save : Nil
@query_args.push(%("_#{@filename}"))
command = @BIN_LOC.to_s + "/ffmpeg " + @query_args.join(" ")
system command
system(command)
File.delete(@filename)
File.rename("_" + @filename, @filename)

View file

@ -27,21 +27,24 @@ class SpotifySearcher
payload = "grant_type=client_credentials"
response = HTTP::Client.post(auth_url, headers: headers, form: payload)
__error_check(response)
if response.status_code == 200
access_token = JSON.parse(response.body)["access_token"]
@access_header = HTTP::Headers{
"Authorization" => "Bearer #{access_token}"
}
access_token = JSON.parse(response.body)["access_token"]
@access_header = HTTP::Headers{
"Authorization" => "Bearer #{access_token}"
}
@authorized = true
end
@authorized = true
return self
end
# Check if the class is authorized or not
def authorized? : Bool
return @authorized
end
# Searches spotify with the specified parameters for the specified items
#
# ```
@ -59,13 +62,7 @@ class SpotifySearcher
url = @root_url.join("search?q=#{query}").to_s()
response = HTTP::Client.get(url, headers: @access_header)
if response.status_code != 200
puts "There was an error with your request."
puts "Status code: #{response.status_code}"
puts "Reponse: \n#{response.body}"
return nil
end
__error_check(response)
items = JSON.parse(response.body)[item_type + "s"]["items"].as_a
@ -73,10 +70,36 @@ class SpotifySearcher
begin
return items[points[0][1]]
rescue IndexException
rescue IndexError
return nil
end
end
# Find the genre of an artist based off of their id
#
# ```
# SpotifySearcher.new().authorize(...).find_genre("1dfeR4HaWDbWqFHLkxsg1d")
# ```
def find_genre(id : String) : String
url = @root_url.join("artists/#{id}").to_s()
response = HTTP::Client.get(url, headers: @access_header)
__error_check(response)
genre = JSON.parse(response.body)["genres"][0].to_s
genre = genre.split(" ").map { |x| x.capitalize }.join(" ")
return genre
end
# Checks for errors in HTTP requests and raises one if found
private def __error_check(response : HTTP::Client::Response) : Nil
if response.status_code != 200
raise("There was an error with your request.\n" +
"Status code: #{response.status_code}\n" +
"Response: \n#{response.body}")
end
end
# Generates url to run a GET request against to the Spotify open API
# Returns a `String.`

View file

@ -31,7 +31,7 @@ module Youtube
# => "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
# ```
def find_url(song_name : String, artist_name : String, search_terms = "",
download_first = false) : Nil
download_first = false) : String?
query = (song_name + " " + artist_name + " " + search_terms).strip.gsub(" ", "+")
url = "https://www.youtube.com/results?search_query=" + query
@ -51,14 +51,21 @@ module Youtube
ranked = __rank_videos(song_name, artist_name, query, valid_nodes)
return root + valid_nodes[ranked[0]["index"]]["href"]
begin
return root + valid_nodes[ranked[0]["index"]]["href"]
rescue IndexError
return nil
end
end
# Will rank videos according to their title and the user input
# Returns an `Array` of Arrays each layed out like
# [<points>, <original index>].
# Return:
# [
# {"points" => x, "index" => x},
# ...
# ]
private def __rank_videos(song_name : String, artist_name : String,
query : String, nodes : Array(XML::Node)) : Array(Array(Int32))
query : String, nodes : Array(XML::Node)) : Array(Hash(String, Int32))
points = [] of Hash(String, Int32)
index = 0
@ -171,5 +178,6 @@ module Youtube
return true if node["class"].includes?(valid_class)
end
end
return false
end
end