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/
|
||||
/bin/
|
||||
/.shards/
|
||||
/Music/
|
||||
*.dwarf
|
||||
|
||||
*.mp3
|
||||
|
|
|
@ -5,6 +5,7 @@ require "./styles"
|
|||
require "./version"
|
||||
|
||||
require "../glue/song"
|
||||
require "../glue/album"
|
||||
|
||||
|
||||
class CLI
|
||||
|
@ -15,8 +16,9 @@ class CLI
|
|||
[["-h", "--help"], "help", "bool"],
|
||||
[["-v", "--version"], "version", "bool"],
|
||||
[["-i", "--install"], "install", "bool"],
|
||||
[["-a", "--artist"], "artist", "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 "-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 "-s, --song <song>"} Specify song 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.green %(irs --song "Bohemian Rhapsody" --artist "Queen")}
|
||||
|
@ -66,9 +69,14 @@ class CLI
|
|||
exit
|
||||
elsif @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.organize_it(Config.music_directory)
|
||||
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
|
||||
|
||||
|
@ -124,9 +132,15 @@ class CLI
|
|||
end
|
||||
|
||||
private def arg_error(argv : Array(String), arg : Int32, msg : String) : Nil
|
||||
precursor = "irs "
|
||||
precursor = "irs"
|
||||
|
||||
precursor += " " if arg != 0
|
||||
|
||||
if arg == 0
|
||||
start = [] of String
|
||||
else
|
||||
start = argv[..arg - 1]
|
||||
end
|
||||
last = argv[arg + 1..]
|
||||
|
||||
distance = (precursor + start.join(" ")).size
|
||||
|
|
|
@ -6,4 +6,17 @@ module Config
|
|||
path = "~/.irs/bin"
|
||||
return Path[path].expand(home: true).to_s
|
||||
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
|
|
@ -1,9 +1,15 @@
|
|||
require "../bottle/config"
|
||||
|
||||
require "./song"
|
||||
require "./list"
|
||||
|
||||
|
||||
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
|
||||
album = @spotify_searcher.find_item("album", {
|
||||
"name" => @list_name.as(String),
|
||||
|
@ -17,7 +23,43 @@ class Album < SpotifyList
|
|||
end
|
||||
end
|
||||
|
||||
private def set_organization(index : Int32, song : Song)
|
||||
# pass
|
||||
# Will define specific metadata that may not be included in the raw return
|
||||
# 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
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
require "json"
|
||||
|
||||
require "../search/spotify"
|
||||
require "../search/youtube"
|
||||
|
||||
|
@ -13,33 +15,54 @@ abstract class SpotifyList
|
|||
@file_names = [] of String
|
||||
|
||||
def initialize(@list_name : String, @list_author : String?)
|
||||
@spotify_searcher.authorize(
|
||||
"e4198f6a3f7b48029366f22528b5dc66",
|
||||
"ba057d0621a5496bbb64edccf758bde5")
|
||||
end
|
||||
|
||||
# Finds the list, and downloads all of the songs using the `Song` class
|
||||
def grab_it
|
||||
|
||||
if !@spotify_searcher.authorized?
|
||||
raise("Need to call provide_client_keys on Album or Playlist class.")
|
||||
end
|
||||
|
||||
list = find_it()
|
||||
contents = list["tracks"][0]["items"]
|
||||
contents = list["tracks"]["items"].as_a
|
||||
|
||||
i = 0
|
||||
contents.each do |data|
|
||||
if song["track"]?
|
||||
data = data["track"]
|
||||
contents.each do |datum|
|
||||
if datum["track"]?
|
||||
datum = datum["track"]
|
||||
end
|
||||
|
||||
data = organize_song_metadata(list, datum)
|
||||
|
||||
song = Song.new(data["name"].to_s, data["artists"][0]["name"].to_s)
|
||||
song.provide_spotify(@spotify_searcher)
|
||||
set_organization(i , song)
|
||||
song.provide_metadata(data)
|
||||
song.grab_it()
|
||||
|
||||
organize(song)
|
||||
|
||||
i += 1
|
||||
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
|
||||
|
||||
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
|
|
@ -11,18 +11,19 @@ class Song
|
|||
@client_secret = ""
|
||||
|
||||
@metadata : JSON::Any?
|
||||
@filename : String?
|
||||
@filename = ""
|
||||
@artist = ""
|
||||
@album = ""
|
||||
|
||||
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
|
||||
def grab_it
|
||||
if !@spotify_searcher.authorized? && !@metadata
|
||||
if @client_id != "" && @client_secret != ""
|
||||
@spotify_searcher.authorize(@client_id, @client_secret)
|
||||
|
@ -47,9 +48,10 @@ class Song
|
|||
end
|
||||
|
||||
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 ..."
|
||||
# TODO: should this search_term be here?
|
||||
url = Youtube.find_url(@song_name, @artist_name, search_terms: "lyrics")
|
||||
|
||||
if !url
|
||||
|
@ -59,18 +61,21 @@ class Song
|
|||
end
|
||||
|
||||
puts "Downloading video:"
|
||||
Ripper.download_mp3(url.as(String), filename)
|
||||
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)
|
||||
@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_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("artist", @artist)
|
||||
tagger.add_text_tag("album", @album)
|
||||
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)
|
||||
|
@ -84,6 +89,28 @@ class Song
|
|||
|
||||
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
|
||||
# metadata. Must be called if provide_client_keys and provide_spotify are not
|
||||
# called.
|
||||
|
@ -105,7 +132,7 @@ class Song
|
|||
# .authenticate("XXXXXXXXXX", "XXXXXXXXXXX")).grab_it()
|
||||
# ```
|
||||
def provide_spotify(spotify : SpotifySearcher) : self
|
||||
@spotify = spotify
|
||||
@spotify_searcher = spotify
|
||||
return self
|
||||
end
|
||||
|
||||
|
@ -121,7 +148,3 @@ class Song
|
|||
return self
|
||||
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
|
||||
|
||||
# An internal class that will keep track of what to output to the user or
|
||||
# what should be hidden.
|
||||
private class RipperOutputCensor
|
||||
@dl_status_index = 0
|
||||
|
||||
|
|
|
@ -218,8 +218,8 @@ end
|
|||
|
||||
|
||||
# puts SpotifySearcher.new()
|
||||
# .authorize("e4198f6a3f7b48029366f22528b5dc66",
|
||||
# "ba057d0621a5496bbb64edccf758bde5")
|
||||
# .authorize("XXXXXXXXXXXXXXX",
|
||||
# "XXXXXXXXXXXXXXX")
|
||||
# .find_item("playlist", {
|
||||
# "name" => "Brain Food",
|
||||
# "username" => "spotify"
|
||||
|
|
Loading…
Reference in a new issue