mirror of
https://github.com/cooperhammond/irs.git
synced 2025-01-17 21:17:07 +00:00
Cleaned up list inheritance, songs are automatically organized
Moved more permanent variables to the config. I need to start thinking about custom configs rather than a hard-coded one.
This commit is contained in:
parent
a24703d7bf
commit
5881d21f48
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -2,6 +2,7 @@
|
||||||
/lib/
|
/lib/
|
||||||
/bin/
|
/bin/
|
||||||
/.shards/
|
/.shards/
|
||||||
|
/Music/
|
||||||
*.dwarf
|
*.dwarf
|
||||||
|
|
||||||
*.mp3
|
*.mp3
|
||||||
|
|
|
@ -5,6 +5,7 @@ require "./styles"
|
||||||
require "./version"
|
require "./version"
|
||||||
|
|
||||||
require "../glue/song"
|
require "../glue/song"
|
||||||
|
require "../glue/album"
|
||||||
|
|
||||||
|
|
||||||
class CLI
|
class CLI
|
||||||
|
@ -15,8 +16,9 @@ class CLI
|
||||||
[["-h", "--help"], "help", "bool"],
|
[["-h", "--help"], "help", "bool"],
|
||||||
[["-v", "--version"], "version", "bool"],
|
[["-v", "--version"], "version", "bool"],
|
||||||
[["-i", "--install"], "install", "bool"],
|
[["-i", "--install"], "install", "bool"],
|
||||||
|
[["-a", "--artist"], "artist", "string"],
|
||||||
[["-s", "--song"], "song", "string"],
|
[["-s", "--song"], "song", "string"],
|
||||||
[["-a", "--artist"], "artist", "string"]
|
[["-A", "--album"], "album", "string"]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -38,8 +40,9 @@ class CLI
|
||||||
#{Style.blue "-h, --help"} Show this help message and exit
|
#{Style.blue "-h, --help"} Show this help message and exit
|
||||||
#{Style.blue "-v, --version"} Show the program version and exit
|
#{Style.blue "-v, --version"} Show the program version and exit
|
||||||
#{Style.blue "-i, --install"} Download ffmpeg and youtube_dl binaries to #{Style.green Config.binary_location}
|
#{Style.blue "-i, --install"} Download ffmpeg and youtube_dl binaries to #{Style.green Config.binary_location}
|
||||||
#{Style.blue "-s, --song <song>"} Specify song name for downloading
|
|
||||||
#{Style.blue "-a, --artist <artist>"} Specify artist name for downloading
|
#{Style.blue "-a, --artist <artist>"} Specify artist name for downloading
|
||||||
|
#{Style.blue "-s, --song <song>"} Specify song name to download
|
||||||
|
#{Style.blue "-A, --album <album"} Specify the album name to download
|
||||||
|
|
||||||
#{Style.bold "Examples:"}
|
#{Style.bold "Examples:"}
|
||||||
$ #{Style.green %(irs --song "Bohemian Rhapsody" --artist "Queen")}
|
$ #{Style.green %(irs --song "Bohemian Rhapsody" --artist "Queen")}
|
||||||
|
@ -66,9 +69,14 @@ class CLI
|
||||||
exit
|
exit
|
||||||
elsif @args["song"]? && @args["artist"]?
|
elsif @args["song"]? && @args["artist"]?
|
||||||
s = Song.new(@args["song"], @args["artist"])
|
s = Song.new(@args["song"], @args["artist"])
|
||||||
s.provide_client_keys("e4198f6a3f7b48029366f22528b5dc66", "ba057d0621a5496bbb64edccf758bde5")
|
s.provide_client_keys(Config.client_key, Config.client_secret)
|
||||||
s.grab_it()
|
s.grab_it()
|
||||||
|
s.organize_it(Config.music_directory)
|
||||||
exit
|
exit
|
||||||
|
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()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -124,9 +132,15 @@ class CLI
|
||||||
end
|
end
|
||||||
|
|
||||||
private def arg_error(argv : Array(String), arg : Int32, msg : String) : Nil
|
private def arg_error(argv : Array(String), arg : Int32, msg : String) : Nil
|
||||||
precursor = "irs "
|
precursor = "irs"
|
||||||
|
|
||||||
start = argv[..arg - 1]
|
precursor += " " if arg != 0
|
||||||
|
|
||||||
|
if arg == 0
|
||||||
|
start = [] of String
|
||||||
|
else
|
||||||
|
start = argv[..arg - 1]
|
||||||
|
end
|
||||||
last = argv[arg + 1..]
|
last = argv[arg + 1..]
|
||||||
|
|
||||||
distance = (precursor + start.join(" ")).size
|
distance = (precursor + start.join(" ")).size
|
||||||
|
|
|
@ -6,4 +6,17 @@ module Config
|
||||||
path = "~/.irs/bin"
|
path = "~/.irs/bin"
|
||||||
return Path[path].expand(home: true).to_s
|
return Path[path].expand(home: true).to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def music_directory : String
|
||||||
|
path = "./Music/"
|
||||||
|
return Path[path].expand(home: true).to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
def client_key : String
|
||||||
|
return "e4198f6a3f7b48029366f22528b5dc66"
|
||||||
|
end
|
||||||
|
|
||||||
|
def client_secret : String
|
||||||
|
return "ba057d0621a5496bbb64edccf758bde5"
|
||||||
|
end
|
||||||
end
|
end
|
|
@ -1,9 +1,15 @@
|
||||||
|
require "../bottle/config"
|
||||||
|
|
||||||
require "./song"
|
require "./song"
|
||||||
require "./list"
|
require "./list"
|
||||||
|
|
||||||
|
|
||||||
class Album < SpotifyList
|
class Album < SpotifyList
|
||||||
|
|
||||||
|
@music_directory = Config.music_directory
|
||||||
|
|
||||||
|
# Uses the `spotify_searcher` defined in parent `SpotifyList` to find the
|
||||||
|
# correct metadata of the list
|
||||||
def find_it
|
def find_it
|
||||||
album = @spotify_searcher.find_item("album", {
|
album = @spotify_searcher.find_item("album", {
|
||||||
"name" => @list_name.as(String),
|
"name" => @list_name.as(String),
|
||||||
|
@ -17,7 +23,43 @@ class Album < SpotifyList
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private def set_organization(index : Int32, song : Song)
|
# Will define specific metadata that may not be included in the raw return
|
||||||
# pass
|
# of spotify's album json. Moves the title of the album and the album art
|
||||||
|
# to the json of the single song
|
||||||
|
def organize_song_metadata(list : JSON::Any, datum : JSON::Any) : JSON::Any
|
||||||
|
json_string = %(
|
||||||
|
{
|
||||||
|
"album": {
|
||||||
|
"name": "#{list["name"]}",
|
||||||
|
"images": [{"url": "#{list["images"][0]["url"]}"}]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
datum.as_h.keys.each_with_index do |key, index|
|
||||||
|
value = datum[key]
|
||||||
|
if value.as_s?
|
||||||
|
json_string += %("#{key}": "#{datum[key]}")
|
||||||
|
else
|
||||||
|
json_string += %("#{key}": #{datum[key].to_s.gsub(" => ", ": ")})
|
||||||
|
end
|
||||||
|
|
||||||
|
if index != datum.as_h.keys.size - 1
|
||||||
|
json_string += ",\n"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
json_string += %(
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
json_string = json_string.gsub(" ", "")
|
||||||
|
json_string = json_string.gsub("\n", " ")
|
||||||
|
json_string = json_string.gsub("\t", "")
|
||||||
|
|
||||||
|
data = JSON.parse(json_string)
|
||||||
|
|
||||||
|
return data
|
||||||
|
end
|
||||||
|
|
||||||
|
private def organize(song : Song)
|
||||||
|
song.organize_it(@music_directory)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
require "json"
|
||||||
|
|
||||||
require "../search/spotify"
|
require "../search/spotify"
|
||||||
require "../search/youtube"
|
require "../search/youtube"
|
||||||
|
|
||||||
|
@ -13,33 +15,54 @@ abstract class SpotifyList
|
||||||
@file_names = [] of String
|
@file_names = [] of String
|
||||||
|
|
||||||
def initialize(@list_name : String, @list_author : String?)
|
def initialize(@list_name : String, @list_author : String?)
|
||||||
@spotify_searcher.authorize(
|
|
||||||
"e4198f6a3f7b48029366f22528b5dc66",
|
|
||||||
"ba057d0621a5496bbb64edccf758bde5")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Finds the list, and downloads all of the songs using the `Song` class
|
# Finds the list, and downloads all of the songs using the `Song` class
|
||||||
def grab_it
|
def grab_it
|
||||||
|
|
||||||
|
if !@spotify_searcher.authorized?
|
||||||
|
raise("Need to call provide_client_keys on Album or Playlist class.")
|
||||||
|
end
|
||||||
|
|
||||||
list = find_it()
|
list = find_it()
|
||||||
contents = list["tracks"][0]["items"]
|
contents = list["tracks"]["items"].as_a
|
||||||
|
|
||||||
i = 0
|
i = 0
|
||||||
contents.each do |data|
|
contents.each do |datum|
|
||||||
if song["track"]?
|
if datum["track"]?
|
||||||
data = data["track"]
|
datum = datum["track"]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
data = organize_song_metadata(list, datum)
|
||||||
|
|
||||||
song = Song.new(data["name"].to_s, data["artists"][0]["name"].to_s)
|
song = Song.new(data["name"].to_s, data["artists"][0]["name"].to_s)
|
||||||
song.provide_spotify(@spotify_searcher)
|
song.provide_spotify(@spotify_searcher)
|
||||||
set_organization(i , song)
|
song.provide_metadata(data)
|
||||||
song.grab_it()
|
song.grab_it()
|
||||||
|
|
||||||
|
organize(song)
|
||||||
|
|
||||||
i += 1
|
i += 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Will authorize the class associated `SpotifySearcher`
|
||||||
|
def provide_client_keys(client_key : String, client_secret : String)
|
||||||
|
@spotify_searcher.authorize(client_key, client_secret)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Defined in subclasses, will return the appropriate information or call an
|
||||||
|
# error if the info is not found and exit
|
||||||
abstract def find_it : JSON::Any
|
abstract def find_it : JSON::Any
|
||||||
|
|
||||||
private abstract def set_organization(song_index : Int32, song : Song)
|
# If there's a need to organize the individual song data so that the `Song`
|
||||||
|
# class can better handle it, this function will be defined in the subclass
|
||||||
|
private abstract def organize_song_metadata(list : JSON::Any,
|
||||||
|
datum : JSON::Any) : JSON::Any
|
||||||
|
|
||||||
|
# Will define the specific type of organization for a list of songs.
|
||||||
|
# Needed because most people want albums sorted by artist, but playlists all
|
||||||
|
# in one folder
|
||||||
|
private abstract def organize(song : Song)
|
||||||
|
|
||||||
end
|
end
|
|
@ -11,18 +11,19 @@ class Song
|
||||||
@client_secret = ""
|
@client_secret = ""
|
||||||
|
|
||||||
@metadata : JSON::Any?
|
@metadata : JSON::Any?
|
||||||
@filename : String?
|
@filename = ""
|
||||||
|
@artist = ""
|
||||||
|
@album = ""
|
||||||
|
|
||||||
def initialize(@song_name : String, @artist_name : String)
|
def initialize(@song_name : String, @artist_name : String)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Find, downloads, and tags the mp3 song that this class represents.
|
# 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()
|
# Song.new("Bohemian Rhapsody", "Queen").grab_it()
|
||||||
# ```
|
# ```
|
||||||
def grab_it : Nil
|
def grab_it
|
||||||
if !@spotify_searcher.authorized? && !@metadata
|
if !@spotify_searcher.authorized? && !@metadata
|
||||||
if @client_id != "" && @client_secret != ""
|
if @client_id != "" && @client_secret != ""
|
||||||
@spotify_searcher.authorize(@client_id, @client_secret)
|
@spotify_searcher.authorize(@client_id, @client_secret)
|
||||||
|
@ -47,9 +48,10 @@ class Song
|
||||||
end
|
end
|
||||||
|
|
||||||
data = @metadata.as(JSON::Any)
|
data = @metadata.as(JSON::Any)
|
||||||
filename = data["track_number"].to_s + " - #{data["name"].to_s}.mp3"
|
@filename = data["track_number"].to_s + " - #{data["name"].to_s}.mp3"
|
||||||
|
|
||||||
puts "Searching for url ..."
|
puts "Searching for url ..."
|
||||||
|
# TODO: should this search_term be here?
|
||||||
url = Youtube.find_url(@song_name, @artist_name, search_terms: "lyrics")
|
url = Youtube.find_url(@song_name, @artist_name, search_terms: "lyrics")
|
||||||
|
|
||||||
if !url
|
if !url
|
||||||
|
@ -59,18 +61,21 @@ class Song
|
||||||
end
|
end
|
||||||
|
|
||||||
puts "Downloading video:"
|
puts "Downloading video:"
|
||||||
Ripper.download_mp3(url.as(String), filename)
|
Ripper.download_mp3(url.as(String), @filename)
|
||||||
|
|
||||||
temp_albumart_filename = ".tempalbumart.jpg"
|
temp_albumart_filename = ".tempalbumart.jpg"
|
||||||
HTTP::Client.get(data["album"]["images"][0]["url"].to_s) do |response|
|
HTTP::Client.get(data["album"]["images"][0]["url"].to_s) do |response|
|
||||||
File.write(temp_albumart_filename, response.body_io)
|
File.write(temp_albumart_filename, response.body_io)
|
||||||
end
|
end
|
||||||
|
|
||||||
tagger = Tags.new(filename)
|
@artist = data["artists"][0]["name"].to_s
|
||||||
|
@album = data["album"]["name"].to_s
|
||||||
|
|
||||||
|
tagger = Tags.new(@filename)
|
||||||
tagger.add_album_art(temp_albumart_filename)
|
tagger.add_album_art(temp_albumart_filename)
|
||||||
tagger.add_text_tag("title", data["name"].to_s)
|
tagger.add_text_tag("title", data["name"].to_s)
|
||||||
tagger.add_text_tag("artist", data["artists"][0]["name"].to_s)
|
tagger.add_text_tag("artist", @artist)
|
||||||
tagger.add_text_tag("album", data["album"]["name"].to_s)
|
tagger.add_text_tag("album", @album)
|
||||||
tagger.add_text_tag("genre",
|
tagger.add_text_tag("genre",
|
||||||
@spotify_searcher.find_genre(data["artists"][0]["id"].to_s))
|
@spotify_searcher.find_genre(data["artists"][0]["id"].to_s))
|
||||||
tagger.add_text_tag("track", data["track_number"].to_s)
|
tagger.add_text_tag("track", data["track_number"].to_s)
|
||||||
|
@ -84,6 +89,28 @@ class Song
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Will organize the song into the user's provided music directory as
|
||||||
|
# music_directory > artist_name > album_name > song
|
||||||
|
# Must be called AFTER the song has been downloaded.
|
||||||
|
#
|
||||||
|
# ```
|
||||||
|
# s = Song.new("Bohemian Rhapsody", "Queen").grab_it()
|
||||||
|
# s.organize_it("/home/cooper/Music")
|
||||||
|
# # Will move the mp3 file to
|
||||||
|
# # /home/cooper/Music/Queen/A Night At The Opera/1 - Bohemian Rhapsody.mp3
|
||||||
|
# ```
|
||||||
|
def organize_it(music_directory : String)
|
||||||
|
path = Path[music_directory].expand(home: true)
|
||||||
|
path = path / @artist_name.gsub(/[\/]/, "").gsub(" ", " ")
|
||||||
|
path = path / @album.gsub(/[\/]/, "").gsub(" ", " ")
|
||||||
|
strpath = path.to_s
|
||||||
|
if !File.directory?(strpath)
|
||||||
|
FileUtils.mkdir_p(strpath)
|
||||||
|
end
|
||||||
|
safe_filename = @filename.gsub(/[\/]/, "").gsub(" ", " ")
|
||||||
|
File.rename("./" + @filename, (path / safe_filename).to_s)
|
||||||
|
end
|
||||||
|
|
||||||
# Provide metadata so that it doesn't have to find it. Useful for overwriting
|
# 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
|
# metadata. Must be called if provide_client_keys and provide_spotify are not
|
||||||
# called.
|
# called.
|
||||||
|
@ -105,7 +132,7 @@ class Song
|
||||||
# .authenticate("XXXXXXXXXX", "XXXXXXXXXXX")).grab_it()
|
# .authenticate("XXXXXXXXXX", "XXXXXXXXXXX")).grab_it()
|
||||||
# ```
|
# ```
|
||||||
def provide_spotify(spotify : SpotifySearcher) : self
|
def provide_spotify(spotify : SpotifySearcher) : self
|
||||||
@spotify = spotify
|
@spotify_searcher = spotify
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -120,8 +147,4 @@ class Song
|
||||||
@client_secret = client_secret
|
@client_secret = client_secret
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# s = Song.new("Bohemian Rhapsody", "Queen")
|
|
||||||
# s.provide_client_keys("e4198f6a3f7b48029366f22528b5dc66", "ba057d0621a5496bbb64edccf758bde5")
|
|
||||||
# s.grab_it()
|
|
|
@ -44,6 +44,8 @@ module Ripper
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# An internal class that will keep track of what to output to the user or
|
||||||
|
# what should be hidden.
|
||||||
private class RipperOutputCensor
|
private class RipperOutputCensor
|
||||||
@dl_status_index = 0
|
@dl_status_index = 0
|
||||||
|
|
||||||
|
|
|
@ -218,8 +218,8 @@ end
|
||||||
|
|
||||||
|
|
||||||
# puts SpotifySearcher.new()
|
# puts SpotifySearcher.new()
|
||||||
# .authorize("e4198f6a3f7b48029366f22528b5dc66",
|
# .authorize("XXXXXXXXXXXXXXX",
|
||||||
# "ba057d0621a5496bbb64edccf758bde5")
|
# "XXXXXXXXXXXXXXX")
|
||||||
# .find_item("playlist", {
|
# .find_item("playlist", {
|
||||||
# "name" => "Brain Food",
|
# "name" => "Brain Food",
|
||||||
# "username" => "spotify"
|
# "username" => "spotify"
|
||||||
|
|
Loading…
Reference in a new issue