mirror of
https://github.com/cooperhammond/irs.git
synced 2024-11-08 18:38:33 +00:00
song glue done!
This commit is contained in:
parent
f8dc95265f
commit
acd2abb1d0
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -3,3 +3,5 @@
|
|||
/bin/
|
||||
/.shards/
|
||||
*.dwarf
|
||||
|
||||
*.mp3
|
122
src/glue/song.cr
Normal file
122
src/glue/song.cr
Normal 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()
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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.`
|
||||
|
|
|
@ -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
|
Loading…
Reference in a new issue