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 +33 -5
- data/bin/spotify-to-mp3 +152 -55
- data/spotify-to-mp3.gemspec +1 -1
- metadata +6 -6
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).
|
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
|
-
|
15
|
-
|
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
|
10
|
-
def initialize
|
11
|
-
@
|
12
|
-
|
13
|
-
|
9
|
+
class App
|
10
|
+
def initialize
|
11
|
+
@context = {
|
12
|
+
:grooveshark => Grooveshark.new,
|
13
|
+
:spotify => Spotify.new
|
14
|
+
}
|
14
15
|
end
|
15
16
|
|
16
|
-
def
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
-
|
34
|
-
|
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
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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(
|
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
|
-
|
65
|
-
@json = json
|
66
|
-
end
|
183
|
+
attr_reader :name, :artist
|
67
184
|
|
68
|
-
def
|
69
|
-
@json['track']['name']
|
70
|
-
|
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
|
-
|
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
|
data/spotify-to-mp3.gemspec
CHANGED
@@ -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.
|
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.
|
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-
|
12
|
+
date: 2011-10-02 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: grooveshark
|
16
|
-
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: *
|
24
|
+
version_requirements: *70201790964320
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: colorize
|
27
|
-
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: *
|
35
|
+
version_requirements: *70201790963620
|
36
36
|
description: Download MP3 files of Spotify tracks
|
37
37
|
email: francescrosasbosque@gmail.com
|
38
38
|
executables:
|