lastfm-itunes 0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Code Climate](https://codeclimate.com/github/jonaustin/lastfm-itunes.png)](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
|
+
|