spotify-to-mp3 0.4.1 → 0.4.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.
@@ -42,6 +42,10 @@ Make sure you have rubygems installed and configured:
42
42
 
43
43
  ## Changelog
44
44
 
45
+ 2012-01-11
46
+
47
+ - Touch already downloaded songs. This way songs no more in the download list can be spotted easily.
48
+
45
49
  2011-10-03
46
50
 
47
51
  - Make it work on ruby 1.8.7 (Snow Leopard)
@@ -53,11 +57,7 @@ Make sure you have rubygems installed and configured:
53
57
  ## TODO
54
58
 
55
59
  - Consider multiple artists songs
56
- - Estimated download time
57
- - Per song
58
- - Total
59
60
  - Distinguish internal errors from user errors
60
61
  - Filter Grooveshark results by artist, title and length
61
62
  - Split code in multiple files
62
- - --sync option to add/remove \*.mp3 files to match songs file
63
63
  - It seems Grooveshark API fails after hundreds of calls
data/bin/spotify-to-mp3 CHANGED
@@ -1,193 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'rubygems'
4
- require 'grooveshark'
5
- require 'open-uri'
6
- require 'colorize'
7
- require 'cgi'
3
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
8
4
 
9
- class App
10
- def initialize
11
- @context = {
12
- :grooveshark => Grooveshark.new,
13
- :spotify => Spotify.new
14
- }
15
- end
5
+ require 'spotify_to_mp3'
16
6
 
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
45
- end
46
-
47
- private
48
-
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
124
- end
125
-
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
- name = "#{@raw_grooveshark_song.artist} - #{@raw_grooveshark_song.name}.mp3"
148
- name.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
157
- end
158
-
159
- class Grooveshark
160
- def initialize
161
- @client = ::Grooveshark::Client.new
162
- end
163
-
164
- def song_url(song)
165
- @client.get_song_url(song)
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
173
- end
174
-
175
- class Spotify
176
- def track(uri)
177
- content = open('http://ws.spotify.com/lookup/1/.json?uri=' + CGI.escape(uri))
178
- json = JSON.parse(content.string)
179
- Track.new(json)
180
- end
181
-
182
- class Track
183
- attr_reader :name, :artist
184
-
185
- def initialize(json)
186
- @name = json['track']['name']
187
- @artist = json['track']['artists'].first['name']
188
- end
189
- end
190
- end
191
- end
192
-
193
- App.new.run
7
+ SpotifyToMp3::App.new.run
@@ -0,0 +1,58 @@
1
+ require 'rubygems'
2
+ require 'grooveshark'
3
+ require 'open-uri'
4
+ require 'colorize'
5
+ require 'rest-client'
6
+ require 'fileutils'
7
+
8
+ require 'spotify_to_mp3/services'
9
+ require 'spotify_to_mp3/songs'
10
+ require 'spotify_to_mp3/sources'
11
+
12
+ module SpotifyToMp3
13
+
14
+ class App
15
+
16
+ def initialize
17
+ @context = {
18
+ :grooveshark => Grooveshark.new,
19
+ :spotify => Spotify.new
20
+ }
21
+ end
22
+
23
+ def run
24
+ begin
25
+ file = ARGV.first
26
+ raise "No songs file specified. Usage: #{$0} file" if file.nil?
27
+
28
+ File.open(file).each_line{|song_id|
29
+ song_id.strip!
30
+ next if song_id.empty?
31
+ begin
32
+ song = UnresolvedSong.new(@context, song_id)
33
+ print "Resolving \"#{song}\" "
34
+ song = song.resolve
35
+ print "-> Searching \"#{song}\" on Grooveshark "
36
+ song = song.from_grooveshark
37
+ print "-> Downloading \"#{song}\" "
38
+ if File.exists? song.filename
39
+ FileUtils.touch song.filename # To know about songs no longer in download list
40
+ puts "Already exists, skipping".green
41
+ else
42
+ begin
43
+ song.download
44
+ puts "Done".green
45
+ rescue Exception => exception
46
+ puts exception.message.red
47
+ end
48
+ end
49
+ rescue
50
+ puts "#{$!}".red
51
+ end
52
+ }
53
+ rescue
54
+ puts "#{$!}".red
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,40 @@
1
+ require 'cgi'
2
+
3
+ module SpotifyToMp3
4
+
5
+ class Grooveshark
6
+
7
+ def initialize
8
+ @client = ::Grooveshark::Client.new
9
+ end
10
+
11
+ def song_url(song)
12
+ @client.get_song_url(song)
13
+ end
14
+
15
+ def song(query)
16
+ song = @client.search_songs(query).first
17
+ raise "Song not found" if song.nil?
18
+ song
19
+ end
20
+ end
21
+
22
+ class Spotify
23
+
24
+ def track(uri)
25
+ content = open('http://ws.spotify.com/lookup/1/.json?uri=' + CGI.escape(uri))
26
+ json = JSON.parse(content.string)
27
+ Track.new(json)
28
+ end
29
+
30
+ class Track
31
+
32
+ attr_reader :name, :artist
33
+
34
+ def initialize(json)
35
+ @name = json['track']['name']
36
+ @artist = json['track']['artists'].first['name']
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,65 @@
1
+ class UnresolvedSong
2
+
3
+ def initialize(context, id)
4
+ @context = context
5
+ @id = id
6
+ end
7
+
8
+ def resolve
9
+ if @id.start_with?('http://open.spotify.com/track/', 'spotify:track:')
10
+ track = @context[:spotify].track(@id)
11
+ source = SpotifySource.new(@context, track)
12
+ else
13
+ source = PlainSource.new(@context, @id)
14
+ end
15
+ ResolvedSong.new(@context, source)
16
+ end
17
+
18
+ def to_s
19
+ @id
20
+ end
21
+ end
22
+
23
+ class ResolvedSong
24
+
25
+ def initialize(context, source)
26
+ @context = context
27
+ @source = source
28
+ end
29
+
30
+ def from_grooveshark
31
+ GroovesharkSong.new(@context, @source.grooveshark_query)
32
+ end
33
+
34
+ def to_s
35
+ "#{@source}"
36
+ end
37
+ end
38
+
39
+ class GroovesharkSong
40
+
41
+ def initialize(context, query)
42
+ @context = context
43
+ @raw_grooveshark_song = @context[:grooveshark].song(query)
44
+ end
45
+
46
+ def download
47
+ download_url(@context[:grooveshark].song_url(@raw_grooveshark_song))
48
+ end
49
+
50
+ def to_s
51
+ "#{@raw_grooveshark_song.artist} - #{@raw_grooveshark_song.name}"
52
+ end
53
+
54
+ def filename
55
+ name = "#{@raw_grooveshark_song.artist} - #{@raw_grooveshark_song.name}.mp3"
56
+ name.tr('/', '-') # / is not allowed in file names
57
+ end
58
+
59
+ private
60
+
61
+ def download_url(url)
62
+ file = RestClient::Request.execute(:method => :post, :url => url, :raw_response => true).file
63
+ FileUtils.mv(file.path, filename)
64
+ end
65
+ end
@@ -0,0 +1,43 @@
1
+ class Source
2
+
3
+ def grooveshark_query
4
+ raise "Not implemented"
5
+ end
6
+
7
+ def to_s
8
+ raise "Not implemented"
9
+ end
10
+ end
11
+
12
+ class SpotifySource < Source
13
+
14
+ def initialize(context, track)
15
+ @context = context
16
+ @track = track
17
+ end
18
+
19
+ def grooveshark_query
20
+ "artist:\"#{@track.artist}\" title:\"#{@track.name}\""
21
+ end
22
+
23
+ def to_s
24
+ "#{@track.artist} - #{@track.name}"
25
+ end
26
+ end
27
+
28
+ class PlainSource < Source
29
+
30
+ attr_reader :grooveshark_query, :to_s
31
+
32
+ def initialize(context, id)
33
+ parts = id.split(' - ', 2)
34
+ if parts.length < 2
35
+ @grooveshark_query = id
36
+ @to_s = id
37
+ else
38
+ artist, name = parts
39
+ @grooveshark_query = "artist:\"#{artist}\" title:\"#{name}\""
40
+ @to_s = "#{artist} - #{name}"
41
+ end
42
+ end
43
+ end
@@ -2,13 +2,14 @@ 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.4.1'
5
+ s.version = '0.4.2'
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'
9
9
 
10
10
  s.add_dependency 'grooveshark'
11
11
  s.add_dependency 'colorize'
12
+ s.add_dependency 'rest-client'
12
13
 
13
14
  s.files = `git ls-files`.split("\n")
14
15
  s.executables = `git ls-files -- bin/*`.split("\n").map{|f| File.basename(f)}
metadata CHANGED
@@ -1,96 +1,102 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: spotify-to-mp3
3
- version: !ruby/object:Gem::Version
4
- hash: 13
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.2
5
5
  prerelease:
6
- segments:
7
- - 0
8
- - 4
9
- - 1
10
- version: 0.4.1
11
6
  platform: ruby
12
- authors:
13
- - "Francesc Ros\xC3\xA0s"
7
+ authors:
8
+ - Francesc Rosàs
14
9
  autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
-
18
- date: 2011-10-03 00:00:00 Z
19
- dependencies:
20
- - !ruby/object:Gem::Dependency
12
+ date: 2012-08-19 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
21
15
  name: grooveshark
22
- prerelease: false
23
- requirement: &id001 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
24
17
  none: false
25
- requirements:
26
- - - ">="
27
- - !ruby/object:Gem::Version
28
- hash: 3
29
- segments:
30
- - 0
31
- version: "0"
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
32
22
  type: :runtime
33
- version_requirements: *id001
34
- - !ruby/object:Gem::Dependency
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
35
31
  name: colorize
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
36
39
  prerelease: false
37
- requirement: &id002 !ruby/object:Gem::Requirement
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rest-client
48
+ requirement: !ruby/object:Gem::Requirement
38
49
  none: false
39
- requirements:
40
- - - ">="
41
- - !ruby/object:Gem::Version
42
- hash: 3
43
- segments:
44
- - 0
45
- version: "0"
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
46
54
  type: :runtime
47
- version_requirements: *id002
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
48
62
  description: Download MP3 files of Spotify tracks
49
63
  email: francescrosasbosque@gmail.com
50
- executables:
64
+ executables:
51
65
  - spotify-to-mp3
52
66
  extensions: []
53
-
54
67
  extra_rdoc_files: []
55
-
56
- files:
68
+ files:
57
69
  - Gemfile
58
70
  - Gemfile.lock
59
- - README.markdown
71
+ - README.md
60
72
  - bin/spotify-to-mp3
73
+ - lib/spotify_to_mp3.rb
74
+ - lib/spotify_to_mp3/services.rb
75
+ - lib/spotify_to_mp3/songs.rb
76
+ - lib/spotify_to_mp3/sources.rb
61
77
  - spotify-to-mp3.gemspec
62
78
  homepage: https://github.com/frosas/spotify-to-mp3
63
79
  licenses: []
64
-
65
80
  post_install_message:
66
81
  rdoc_options: []
67
-
68
- require_paths:
82
+ require_paths:
69
83
  - .
70
- required_ruby_version: !ruby/object:Gem::Requirement
84
+ required_ruby_version: !ruby/object:Gem::Requirement
71
85
  none: false
72
- requirements:
73
- - - ">="
74
- - !ruby/object:Gem::Version
75
- hash: 3
76
- segments:
77
- - 0
78
- version: "0"
79
- required_rubygems_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ! '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
91
  none: false
81
- requirements:
82
- - - ">="
83
- - !ruby/object:Gem::Version
84
- hash: 3
85
- segments:
86
- - 0
87
- version: "0"
92
+ requirements:
93
+ - - ! '>='
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
88
96
  requirements: []
89
-
90
97
  rubyforge_project:
91
- rubygems_version: 1.8.6
98
+ rubygems_version: 1.8.24
92
99
  signing_key:
93
100
  specification_version: 3
94
101
  summary: Spotify to MP3
95
102
  test_files: []
96
-