lastfm-itunes 0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +1 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +72 -0
- data/Rakefile +9 -0
- data/bin/lastfm-itunes +58 -0
- data/lastfm-itunes.gemspec +35 -0
- data/lib/lastfm-itunes.rb +15 -0
- data/lib/lastfm-itunes/cli.rb +109 -0
- data/lib/lastfm-itunes/generator.rb +19 -0
- data/lib/lastfm-itunes/itunes.rb +27 -0
- data/lib/lastfm-itunes/lastfm/artist.rb +30 -0
- data/lib/lastfm-itunes/lastfm/search.rb +15 -0
- data/lib/lastfm-itunes/lastfm/user.rb +21 -0
- data/lib/lastfm-itunes/patches/m3uzi.rb +5 -0
- data/lib/lastfm-itunes/patches/rockstar.rb +9 -0
- data/lib/lastfm-itunes/playlist.rb +25 -0
- data/lib/lastfm-itunes/tracks.rb +35 -0
- data/lib/lastfm-itunes/version.rb +3 -0
- data/spec/fixtures/iTunes Music Library Remote.xml +124 -0
- data/spec/fixtures/iTunes Music Library.xml +5763 -0
- data/spec/fixtures/vcr/lastfm_top_tracks.yml +612 -0
- data/spec/fixtures/vcr/lastfm_top_tracks_for_all_itunes_artists.yml +8837 -0
- data/spec/fixtures/vcr/lastfm_top_tracks_for_all_itunes_artists_with_limit.yml +8853 -0
- data/spec/fixtures/vcr/lastfm_user_top_tracks.yml +9918 -0
- data/spec/lastfm-itunes/generator_spec.rb +31 -0
- data/spec/lastfm-itunes/itunes_spec.rb +33 -0
- data/spec/lastfm-itunes/lastfm/artist_spec.rb +41 -0
- data/spec/lastfm-itunes/lastfm/search_spec.rb +31 -0
- data/spec/lastfm-itunes/lastfm/user_spec.rb +41 -0
- data/spec/lastfm-itunes/playlist_spec.rb +29 -0
- data/spec/spec_helper.rb +47 -0
- metadata +275 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0ac7d667b28a195b056b922d3dcc6c001c48ad08
|
4
|
+
data.tar.gz: 73cb90b8cd591c1ce0efc622573b10652cde7233
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6ba65237434407015611f2d4fb82ec9e1e28a0e68876c58630130a4ea95c813bad2470028c7177ed32b5678edef7da45a75e8b5bb5a61d4214a2c22ca82ac531
|
7
|
+
data.tar.gz: 6212eb19e200402efad603804b6d70710df13557f4c2a3e4be44249609c41d1d87262df04968e945a23879956b6f949fd870e47f9f9a31fd2fda7255fb45843f
|
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Jon Austin
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
[](https://codeclimate.com/github/jonaustin/lastfm-itunes)
|
2
|
+
|
3
|
+
# Lastfm iTunes Playlist Creator
|
4
|
+
|
5
|
+
Easily create playlists of top Lastfm tracks from your iTunes library.
|
6
|
+
|
7
|
+
http://jonaustin.io/posts/lastfm-itunes
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
gem install lastfm-itunes
|
12
|
+
|
13
|
+
## Usage
|
14
|
+
|
15
|
+
lastfm-itunes
|
16
|
+
|
17
|
+
Follow the prompts to generate a playlist:
|
18
|
+
|
19
|
+
|
20
|
+
Lastfm iTunes Playlist Creator
|
21
|
+
==============================
|
22
|
+
|
23
|
+
Where is your iTunes Library XML file? |~/Music/iTunes/iTunes Music Library.xml|
|
24
|
+
Where to store generated playlist? |~/Music/iTunes/Lastfm Top Tracks.m3u|
|
25
|
+
|
26
|
+
Find or create Lastfm API secret and key here: http://www.last.fm/api/accounts
|
27
|
+
What is your Lastfm API Key?
|
28
|
+
What is your Lastfm API Secret?
|
29
|
+
|
30
|
+
1) Artist
|
31
|
+
2) User
|
32
|
+
Search Artist global top tracks or User top tracks? 2
|
33
|
+
Username? echowarpt
|
34
|
+
Maximum number of tracks to fetch? |all| 500
|
35
|
+
|
36
|
+
Extracting from iTunes....
|
37
|
+
Found 64 artists and 1543 tracks
|
38
|
+
Retrieving from Last.fm...
|
39
|
+
Found 309 iTunes tracks out of 500 Last.fm tracks
|
40
|
+
|
41
|
+
Generating Playlist.....
|
42
|
+
|
43
|
+
Success! Your playlist has been generated at: /Users/jon/Music/iTunes/Lastfm Top Tracks.m3u
|
44
|
+
|
45
|
+
## Notes:
|
46
|
+
|
47
|
+
* Each artist in the library requires a separate API call to Lastfm to get the top tracks. As a result generation could several minutes or more if there are hundreds or thousands of artists to fetch.
|
48
|
+
|
49
|
+
* It does not support tracks stored in the cloud. Haven't looked into whether this
|
50
|
+
is possible or not. Feel free to send a pull request.
|
51
|
+
|
52
|
+
* For convenience, lastfm credentials may be stored in ~/.config/lastfm-itunes.yml
|
53
|
+
If that file exists, then it will auto-fill the Lastfm API key and secret.
|
54
|
+
|
55
|
+
api_key: ""
|
56
|
+
api_secret: ""
|
57
|
+
|
58
|
+
## Contributing
|
59
|
+
|
60
|
+
1. Fork it
|
61
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
62
|
+
3. Add tests.
|
63
|
+
4. Commit your changes (`git commit -am 'Add some feature'`)
|
64
|
+
5. Push to the branch (`git push origin my-new-feature`)
|
65
|
+
6. Create new Pull Request
|
66
|
+
|
67
|
+
License
|
68
|
+
-------
|
69
|
+
|
70
|
+
Copyright (c) 2014 Jon Austin.
|
71
|
+
|
72
|
+
Released under the MIT license. See `LICENSE.txt` for details.
|
data/Rakefile
ADDED
data/bin/lastfm-itunes
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib"))
|
3
|
+
require 'lastfm-itunes'
|
4
|
+
require 'lastfm-itunes/cli'
|
5
|
+
|
6
|
+
cli = LastfmItunes::CLI.new
|
7
|
+
|
8
|
+
puts "\nLastfm iTunes Playlist Creator".color(:blue)
|
9
|
+
puts ("="*30).color(:green)
|
10
|
+
puts
|
11
|
+
|
12
|
+
cli.ask_itunes
|
13
|
+
cli.ask_m3u_path
|
14
|
+
puts
|
15
|
+
cli.get_lastfm_credentials
|
16
|
+
|
17
|
+
puts
|
18
|
+
search_type = cli.choose_search_type
|
19
|
+
if search_type == 'Artist'
|
20
|
+
cli.ask_limit("Maximum number of tracks to fetch per artist? ")
|
21
|
+
elsif search_type == 'User'
|
22
|
+
cli.ask_username
|
23
|
+
cli.ask_limit("Maximum number of tracks to fetch? ")
|
24
|
+
end
|
25
|
+
|
26
|
+
args = { itunes_xml_path: cli.itunes_xml_path,
|
27
|
+
credentials: cli.lastfm_credentials,
|
28
|
+
search_class: Object.const_get('LastfmItunes::Lastfm::' + search_type),
|
29
|
+
limit: cli.limit }
|
30
|
+
args.merge!(name: cli.username) if cli.search_type == 'User'
|
31
|
+
|
32
|
+
puts
|
33
|
+
puts "Extracting from iTunes....".color(:green)
|
34
|
+
playlist = LastfmItunes::Playlist.new(args)
|
35
|
+
puts " Found #{playlist.itunes.artists.size} artists and #{playlist.itunes.tracks.size} tracks"
|
36
|
+
|
37
|
+
|
38
|
+
puts "Retrieving from Last.fm...".color(:green)
|
39
|
+
# Artist search requires multiple API queries so show progress bar that increments on
|
40
|
+
# each artist found. (i.e. it could take a few minutes)
|
41
|
+
total_lf_tracks = 0
|
42
|
+
if cli.search_type == 'Artist'
|
43
|
+
progressbar = cli.progressbar(playlist.itunes.artists.size)
|
44
|
+
playlist.fetch_tracks do |num_tracks|
|
45
|
+
total_lf_tracks += (cli.limit && cli.limit < num_tracks ? cli.limit : num_tracks)
|
46
|
+
progressbar.increment
|
47
|
+
end
|
48
|
+
else
|
49
|
+
playlist.fetch_tracks { |num_tracks| total_lf_tracks += num_tracks }
|
50
|
+
end
|
51
|
+
puts " Found #{playlist.tracks.size} iTunes tracks out of #{total_lf_tracks} Last.fm tracks"
|
52
|
+
|
53
|
+
puts "Generating Playlist.......".color(:green)
|
54
|
+
generator = LastfmItunes::Generator.new(cli.m3u_path)
|
55
|
+
generator.generate(playlist.track_paths)
|
56
|
+
|
57
|
+
puts
|
58
|
+
puts "Success! Your playlist has been generated at: ".color(:green) + "#{cli.m3u_path}".color(:yellow)
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'lastfm-itunes/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'lastfm-itunes'
|
8
|
+
spec.version = LastfmItunes::VERSION
|
9
|
+
spec.authors = ['Jon Austin']
|
10
|
+
spec.email = ['jon.i.austin@gmail.com']
|
11
|
+
spec.summary = %q{CLI tool to easily create m3u playlists of top Lastfm tracks from an iTunes library}
|
12
|
+
spec.description = %q{Easily create playlists of top Lastfm tracks from your iTunes library.}
|
13
|
+
spec.homepage = 'https://github.com/jonaustin/lastfm-itunes'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_dependency 'rockstar', '~> 0.8'
|
22
|
+
spec.add_dependency 'rainbow', '~> 1.99'
|
23
|
+
spec.add_dependency 'm3uzi', '~> 0.5'
|
24
|
+
spec.add_dependency 'highline', '~> 1.6'
|
25
|
+
spec.add_dependency 'ruby-progressbar', '~> 1.4'
|
26
|
+
spec.add_dependency 'andyw8-itunes-library', '~> 0.1' # original does not have track.location* methods
|
27
|
+
|
28
|
+
spec.add_development_dependency 'rake'
|
29
|
+
spec.add_development_dependency 'bundler', '~> 1.3'
|
30
|
+
spec.add_development_dependency 'rspec', '~> 2.14'
|
31
|
+
spec.add_development_dependency 'simplecov', '~> 0.8'
|
32
|
+
spec.add_development_dependency 'pry-byebug', '~> 2.2'
|
33
|
+
spec.add_development_dependency 'vcr', '~> 2.8'
|
34
|
+
spec.add_development_dependency 'webmock', '~> 1.16'
|
35
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'rainbow'
|
2
|
+
require 'pathname'
|
3
|
+
require 'rockstar'
|
4
|
+
require 'lastfm-itunes/patches/rockstar'
|
5
|
+
require 'lastfm-itunes/tracks'
|
6
|
+
require 'lastfm-itunes/playlist'
|
7
|
+
require 'lastfm-itunes/generator'
|
8
|
+
require 'lastfm-itunes/itunes'
|
9
|
+
require 'lastfm-itunes/lastfm/search'
|
10
|
+
require 'lastfm-itunes/lastfm/user'
|
11
|
+
require 'lastfm-itunes/lastfm/artist'
|
12
|
+
require 'lastfm-itunes/version'
|
13
|
+
|
14
|
+
module LastfmItunes
|
15
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'highline/import'
|
3
|
+
require 'ruby-progressbar'
|
4
|
+
|
5
|
+
module LastfmItunes
|
6
|
+
class CLI
|
7
|
+
attr_reader :itunes_xml_path, :m3u_path, :limit
|
8
|
+
attr_reader :lastfm_credentials, :username, :search_type
|
9
|
+
|
10
|
+
def ask_itunes
|
11
|
+
@itunes_xml_path = ask("Where is your iTunes Library XML file? ") do |q|
|
12
|
+
q.default = File.expand_path("~/Music/iTunes/iTunes Music Library.xml")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def ask_m3u_path(options={})
|
17
|
+
m3u_path = ask("Where to store generated playlist? ") do |q|
|
18
|
+
q.default = options.fetch(:m3u_path, m3u_default_path.to_s)
|
19
|
+
end
|
20
|
+
|
21
|
+
if File.exists?(m3u_path)
|
22
|
+
answer = ask_overwrite_m3u
|
23
|
+
if answer == 'No'
|
24
|
+
m3u_path = ask_m3u_path(m3u_path: with_time(m3u_path))
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
@m3u_path = m3u_path
|
29
|
+
end
|
30
|
+
|
31
|
+
def get_lastfm_credentials
|
32
|
+
puts "Find or create Lastfm API secret and key here: http://www.last.fm/api/accounts".color(:yellow)
|
33
|
+
|
34
|
+
lastfm_yml_path = File.expand_path("~/.config/lastfm-itunes.yml")
|
35
|
+
lastfm_credentials = if File.exists?(lastfm_yml_path)
|
36
|
+
YAML.load_file(lastfm_yml_path)
|
37
|
+
else
|
38
|
+
{}
|
39
|
+
end
|
40
|
+
@lastfm_credentials = { api_key: ask_api_key(lastfm_credentials),
|
41
|
+
api_secret: ask_api_secret(lastfm_credentials) }
|
42
|
+
end
|
43
|
+
|
44
|
+
def ask_api_key(credentials=nil)
|
45
|
+
ask("What is your Lastfm API Key? ") do |q|
|
46
|
+
q.default = credentials['api_key'] if credentials
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def ask_api_secret(credentials=nil)
|
51
|
+
ask("What is your Lastfm API Secret? ") do |q|
|
52
|
+
q.default = credentials['api_secret'] if credentials
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def choose_search_type
|
57
|
+
@search_type = choose do |menu|
|
58
|
+
menu.index = :number
|
59
|
+
menu.index_suffix = ") "
|
60
|
+
|
61
|
+
menu.prompt = "Search Artist global top tracks or User top tracks? "
|
62
|
+
|
63
|
+
menu.choice "Artist"
|
64
|
+
menu.choice "User"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def ask_limit(question_text)
|
69
|
+
limit = ask(question_text) do |q|
|
70
|
+
q.default = 'all'
|
71
|
+
end
|
72
|
+
@limit = limit.to_i > 0 ? limit.to_i : nil
|
73
|
+
end
|
74
|
+
|
75
|
+
def ask_username
|
76
|
+
@username = ask("Username? ") do |q|
|
77
|
+
q.validate = /\A\w+\Z/
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def ask_overwrite_m3u
|
82
|
+
choose do |menu|
|
83
|
+
menu.index = :number
|
84
|
+
menu.index_suffix = ") "
|
85
|
+
|
86
|
+
menu.prompt = "Overwite existing playlist file? "
|
87
|
+
|
88
|
+
menu.choice "Yes"
|
89
|
+
menu.choice "No"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def progressbar(total)
|
94
|
+
ProgressBar.create(
|
95
|
+
starting_at: 0,
|
96
|
+
total: total,
|
97
|
+
format: "%a".color(:cyan) + "|".color(:magenta) + "%B".color(:green) + "|".color(:magenta) + "%p%%".color(:cyan))
|
98
|
+
end
|
99
|
+
|
100
|
+
def m3u_default_path
|
101
|
+
Pathname(@itunes_xml_path).dirname.join('Lastfm Top Tracks.m3u')
|
102
|
+
end
|
103
|
+
|
104
|
+
def with_time(filepath)
|
105
|
+
time_str = Time.now.strftime("%Y-%m-%d_%H%M%S")
|
106
|
+
filepath.gsub(/\.m3u\Z/, " #{time_str}.m3u")
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'm3uzi'
|
2
|
+
require 'lastfm-itunes/patches/m3uzi'
|
3
|
+
|
4
|
+
module LastfmItunes
|
5
|
+
class Generator
|
6
|
+
attr_reader :m3u
|
7
|
+
|
8
|
+
def initialize(m3u_path)
|
9
|
+
@m3u = ::M3Uzi.new
|
10
|
+
@m3u.path = File.expand_path(m3u_path)
|
11
|
+
end
|
12
|
+
|
13
|
+
def generate(tracks, &block)
|
14
|
+
tracks.each { |t| @m3u.add_file(t) }
|
15
|
+
@m3u.write @m3u.path
|
16
|
+
@m3u
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'itunes/library'
|
2
|
+
|
3
|
+
module LastfmItunes
|
4
|
+
class Itunes
|
5
|
+
include Tracks
|
6
|
+
attr_accessor :library, :tracks, :xml_path
|
7
|
+
|
8
|
+
def initialize(xml_path)
|
9
|
+
@xml_path = File.expand_path(xml_path)
|
10
|
+
@library = ::ITunes::Library.load(@xml_path)
|
11
|
+
raise InvalidLibraryException unless library.track_ids
|
12
|
+
@tracks = @library.music.tracks.select { |t| t.location_path }
|
13
|
+
end
|
14
|
+
|
15
|
+
alias :old_group_tracks_by_artist :group_tracks_by_artist
|
16
|
+
def group_tracks_by_artist
|
17
|
+
old_group_tracks_by_artist(@tracks)
|
18
|
+
end
|
19
|
+
|
20
|
+
def artists
|
21
|
+
@tracks.map(&:artist).uniq
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class InvalidLibraryException < Exception; end
|
26
|
+
end
|
27
|
+
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module LastfmItunes::Lastfm
|
2
|
+
class Artist
|
3
|
+
include LastfmItunes::Tracks
|
4
|
+
attr_accessor :artist, :limit
|
5
|
+
|
6
|
+
def initialize(args={})
|
7
|
+
@artist = if args[:name]
|
8
|
+
::Rockstar::Artist.new( args.fetch(:name) )
|
9
|
+
else
|
10
|
+
nil
|
11
|
+
end
|
12
|
+
@limit = args.fetch(:limit, 50) # default number returned by lastfm
|
13
|
+
end
|
14
|
+
|
15
|
+
def my_top_tracks(other_tracks, &block)
|
16
|
+
found_tracks = []
|
17
|
+
group_tracks_by_artist(other_tracks).each do |artist, tracks|
|
18
|
+
next unless artist
|
19
|
+
yield tracks.size if block_given?
|
20
|
+
@artist = Rockstar::Artist.new(artist)
|
21
|
+
found_tracks.concat intersection_of_tracks(top_tracks, tracks)
|
22
|
+
end
|
23
|
+
found_tracks
|
24
|
+
end
|
25
|
+
|
26
|
+
def top_tracks
|
27
|
+
artist.top_tracks
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'delegate'
|
3
|
+
|
4
|
+
module LastfmItunes::Lastfm
|
5
|
+
class Search < SimpleDelegator
|
6
|
+
extend Forwardable
|
7
|
+
delegate [:my_top_tracks, :top_tracks] => :@searcher
|
8
|
+
|
9
|
+
def initialize(args)
|
10
|
+
::Rockstar.lastfm = args.fetch(:credentials)
|
11
|
+
@searcher = args.fetch(:search_class, Artist).new(args)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|