spotify-to-mp3 0.3.1 → 0.4

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.
data/README.markdown CHANGED
@@ -4,25 +4,53 @@ A simple command line utility to download MP3 files of Spotify tracks
4
4
 
5
5
  ## Installation
6
6
 
7
+ ### Mac OS X (Lion)
8
+
7
9
  $ gem install spotify-to-mp3
8
10
 
11
+ ### Linux (Ubuntu 11.04)
12
+
13
+ Make sure you have rubygems installed and configured:
14
+
15
+ $ sudo apt-get install rubygems1.8
16
+ $ echo 'PATH=$PATH:/var/lib/gems/1.8/bin' | sudo tee /etc/profile.d/rubygems1.8.sh >/dev/null
17
+
18
+ <span></span>
19
+
20
+ $ sudo gem install spotify-to-mp3
21
+
22
+ ### Windows (not tested)
23
+
24
+ [Install Ruby](http://rubyinstaller.org/)
25
+
26
+ > gem install spotify-to-mp3
27
+
9
28
  ## Usage
10
29
 
11
- 1. Drag Spotify songs to a file (eg, songs.txt). It will look like this:
30
+ 1. Drag Spotify songs to a file (eg, songs.txt). Plain song names are also
31
+ accepted. It may look like this:
12
32
 
13
33
  http://open.spotify.com/track/1JqTcOjOn7gEpeC0JcRVPa
14
- http://open.spotify.com/track/4pQIl2gJyRDKiwIhEOc1gW
15
- http://open.spotify.com/track/2rREB1sRvcb5i6AZRhOhey
16
- http://open.spotify.com/track/6UOvd5f3oyfIGW3GA8dn89
34
+ spotify:track:1fE3ddAlmjJ99IIfLgZjTy
35
+ The Drums - Money
17
36
 
18
37
  2. Download songs. Errors will appear in red (eg, when a song is not found)
19
38
 
20
39
  $ spotify-to-mp3 songs.txt
21
40
 
41
+ ## Changelog
42
+
43
+ 2011-09-26
44
+
45
+ - Both Spotify URLs and plain song names are accepted
46
+
22
47
  ## TODO
23
48
 
24
49
  - Consider multiple artists songs
25
- - Show download progress
26
50
  - Estimated download time
27
51
  - Per song
28
52
  - Total
53
+ - Distinguish internal errors from user errors
54
+ - Filter Grooveshark results by artist, title and length
55
+ - Split code in multiple files
56
+ - --sync option to add/remove \*.mp3 files to match songs file
data/bin/spotify-to-mp3 CHANGED
@@ -6,39 +6,154 @@ require 'open-uri'
6
6
  require 'colorize'
7
7
  require 'cgi'
8
8
 
9
- class SpotifyToMp3
10
- def initialize(log)
11
- @grooveshark = Grooveshark.new
12
- @spotify = Spotify.new
13
- @log = log
9
+ class App
10
+ def initialize
11
+ @context = {
12
+ :grooveshark => Grooveshark.new,
13
+ :spotify => Spotify.new
14
+ }
14
15
  end
15
16
 
16
- def download_song(spotify_uri)
17
- log("Getting song info for #{spotify_uri} from Spotify")
18
- track = @spotify.track(spotify_uri)
19
- filename = File.basename("#{track.artist()} - #{track.name()}.mp3".tr('/', '-'))
20
- if (File.exists?(filename))
21
- log("File #{filename} already exists, skipping")
22
- return
23
- end
24
- log("Getting download URL for \"#{track.artist()} - #{track.name()}\" from Grooveshark")
25
- url = @grooveshark.song_url(track.artist(), track.name())
26
- log("Downloading song")
27
- download_url(url, filename)
28
- log("Song downloaded to #{filename}")
17
+ def run
18
+ begin
19
+ file = ARGV.first
20
+ raise "No songs file specified. Usage: #{$0} file" if file.nil?
21
+
22
+ File.open(file).each_line{|song_id|
23
+ song_id.strip!
24
+ next if song_id.empty?
25
+ begin
26
+ song = UnresolvedSong.new(@context, song_id)
27
+ print "Resolving \"#{song}\" "
28
+ song = song.resolve
29
+ print "-> Searching \"#{song}\" on Grooveshark "
30
+ song = song.from_grooveshark
31
+ print "-> Downloading \"#{song}\" "
32
+ if song.downloaded
33
+ puts "Already exists, skipping".green
34
+ else
35
+ song.download
36
+ puts "Done".green
37
+ end
38
+ rescue
39
+ puts "#{$!}".red
40
+ end
41
+ }
42
+ rescue
43
+ puts "#{$!}".red
44
+ end
29
45
  end
30
46
 
31
47
  private
32
48
 
33
- def log(message)
34
- @log.call(message)
49
+ class UnresolvedSong
50
+ def initialize(context, id)
51
+ @context = context
52
+ @id = id
53
+ end
54
+
55
+ def resolve
56
+ if @id.start_with?('http://open.spotify.com/track/', 'spotify:track:')
57
+ track = @context[:spotify].track(@id)
58
+ source = SpotifySource.new(@context, track)
59
+ else
60
+ source = PlainSource.new(@context, @id)
61
+ end
62
+ ResolvedSong.new(@context, source)
63
+ end
64
+
65
+ def to_s
66
+ @id
67
+ end
68
+ end
69
+
70
+ class Source
71
+ def grooveshark_query
72
+ raise "Not implemented"
73
+ end
74
+
75
+ def to_s
76
+ raise "Not implemented"
77
+ end
78
+ end
79
+
80
+ class SpotifySource < Source
81
+ def initialize(context, track)
82
+ @context = context
83
+ @track = track
84
+ end
85
+
86
+ def grooveshark_query
87
+ "artist:\"#{@track.artist}\" title:\"#{@track.name}\""
88
+ end
89
+
90
+ def to_s
91
+ "#{@track.artist} - #{@track.name}"
92
+ end
93
+ end
94
+
95
+ class PlainSource < Source
96
+ attr_reader :grooveshark_query, :to_s
97
+
98
+ def initialize(context, id)
99
+ parts = id.split(' - ', 2)
100
+ if parts.length < 2
101
+ @grooveshark_query = id
102
+ @to_s = id
103
+ else
104
+ artist, name = parts
105
+ @grooveshark_query = "artist:\"#{artist}\" title:\"#{name}\""
106
+ @to_s = "#{artist} - #{name}"
107
+ end
108
+ end
109
+ end
110
+
111
+ class ResolvedSong
112
+ def initialize(context, source)
113
+ @context = context
114
+ @source = source
115
+ end
116
+
117
+ def from_grooveshark
118
+ GroovesharkSong.new(@context, @source.grooveshark_query)
119
+ end
120
+
121
+ def to_s
122
+ "#{@source}"
123
+ end
35
124
  end
36
125
 
37
- def download_url(url, path)
38
- content = open(url).read
39
- file = open(path, 'wb')
40
- file.write(content)
41
- file.close
126
+ class GroovesharkSong
127
+ def initialize(context, query)
128
+ @context = context
129
+ @raw_grooveshark_song = @context[:grooveshark].song(query)
130
+ end
131
+
132
+ def downloaded
133
+ File.exists?(filename)
134
+ end
135
+
136
+ def download
137
+ download_url(@context[:grooveshark].song_url(@raw_grooveshark_song))
138
+ end
139
+
140
+ def to_s
141
+ "#{@raw_grooveshark_song.artist} - #{@raw_grooveshark_song.name}"
142
+ end
143
+
144
+ private
145
+
146
+ def filename
147
+ "#{@raw_grooveshark_song.artist} - #{@raw_grooveshark_song.name}.mp3"
148
+ .tr('/', '-') # / is not allowed in file names
149
+ end
150
+
151
+ def download_url(url)
152
+ content = open(url).read
153
+ file = open(filename, 'wb')
154
+ file.write(content)
155
+ file.close
156
+ end
42
157
  end
43
158
 
44
159
  class Grooveshark
@@ -46,11 +161,15 @@ class SpotifyToMp3
46
161
  @client = ::Grooveshark::Client.new
47
162
  end
48
163
 
49
- def song_url(artist, title)
50
- song = @client.search_songs("artist:\"#{artist}\" title:\"#{title}\"").first
51
- raise "Song not found" if song.nil?
164
+ def song_url(song)
52
165
  @client.get_song_url(song)
53
166
  end
167
+
168
+ def song(query)
169
+ song = @client.search_songs(query).first
170
+ raise "Song not found" if song.nil?
171
+ song
172
+ end
54
173
  end
55
174
 
56
175
  class Spotify
@@ -61,36 +180,14 @@ class SpotifyToMp3
61
180
  end
62
181
 
63
182
  class Track
64
- def initialize(json)
65
- @json = json
66
- end
183
+ attr_reader :name, :artist
67
184
 
68
- def name
69
- @json['track']['name']
70
- end
71
-
72
- def artist
73
- @json['track']['artists'].first['name']
185
+ def initialize(json)
186
+ @name = json['track']['name']
187
+ @artist = json['track']['artists'].first['name']
74
188
  end
75
189
  end
76
190
  end
77
191
  end
78
192
 
79
- begin
80
- spotifyToMp3 = SpotifyToMp3.new(lambda { |message| puts message })
81
-
82
- file = ARGV.first
83
- raise "No songs file specified. Usage: #{$0} file" if file.nil?
84
-
85
- File.open(file).each_line{|spotify_uri|
86
- spotify_uri.strip!
87
- next if spotify_uri.empty?
88
- begin
89
- spotifyToMp3.download_song(spotify_uri)
90
- rescue
91
- puts "ERROR: #{$!}".red
92
- end
93
- }
94
- rescue
95
- puts "ERROR: #{$!}".red
96
- end
193
+ App.new.run
@@ -2,7 +2,7 @@ Gem::Specification.new do |s|
2
2
  s.name = 'spotify-to-mp3'
3
3
  s.summary = 'Spotify to MP3'
4
4
  s.description = 'Download MP3 files of Spotify tracks'
5
- s.version = '0.3.1'
5
+ s.version = '0.4'
6
6
  s.author = 'Francesc Rosàs'
7
7
  s.email = 'francescrosasbosque@gmail.com'
8
8
  s.homepage = 'https://github.com/frosas/spotify-to-mp3'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spotify-to-mp3
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: '0.4'
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-09-20 00:00:00.000000000Z
12
+ date: 2011-10-02 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: grooveshark
16
- requirement: &70186426570380 !ruby/object:Gem::Requirement
16
+ requirement: &70201790964320 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70186426570380
24
+ version_requirements: *70201790964320
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: colorize
27
- requirement: &70186426569600 !ruby/object:Gem::Requirement
27
+ requirement: &70201790963620 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,7 +32,7 @@ dependencies:
32
32
  version: '0'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *70186426569600
35
+ version_requirements: *70201790963620
36
36
  description: Download MP3 files of Spotify tracks
37
37
  email: francescrosasbosque@gmail.com
38
38
  executables: