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.
@@ -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
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -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.
@@ -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.
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ # RSpec
4
+ require 'rspec/core'
5
+ require 'rspec/core/rake_task'
6
+ RSpec::Core::RakeTask.new(:spec) do |spec|
7
+ spec.pattern = FileList['spec/**/*_spec.rb']
8
+ end
9
+ task :default => :spec
@@ -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
+