groove-dl 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,34 @@
1
+ .gitignore
2
+ .rubocop.yml
3
+ .travis.yml
4
+ Gemfile
5
+ LICENSE
6
+ MANIFEST
7
+ README.md
8
+ Rakefile
9
+ bin/groove-dl
10
+ bin/groove-dl-cli
11
+ groove-dl.gemspec
12
+ lib/groove-dl.rb
13
+ lib/groove-dl/app.rb
14
+ lib/groove-dl/cli.rb
15
+ lib/groove-dl/cli/search.rb
16
+ lib/groove-dl/configuration.rb
17
+ lib/groove-dl/displayer.rb
18
+ lib/groove-dl/downloader.rb
19
+ lib/groove-dl/errors.rb
20
+ lib/groove-dl/version.rb
21
+ lib/groove-dl/widgets/download/bar.rb
22
+ lib/groove-dl/widgets/download/book.rb
23
+ lib/groove-dl/widgets/download/list/failed.rb
24
+ lib/groove-dl/widgets/download/list/queue.rb
25
+ lib/groove-dl/widgets/download/list/success.rb
26
+ lib/groove-dl/widgets/search/bar.rb
27
+ lib/groove-dl/widgets/search/list.rb
28
+ spec/groove-dl/cli_spec.rb
29
+ spec/groove-dl/displayer_spec.rb
30
+ spec/groove-dl/downloader_spec.rb
31
+ spec/spec_helper.rb
32
+ task/manifest.rake
33
+ task/rubocop.rake
34
+ task/spec.rake
@@ -0,0 +1,87 @@
1
+ #Grooveshark song downloader
2
+
3
+ [![Build Status](https://travis-ci.org/PierreRambaud/groove-dl.png?branch=master)](https://travis-ci.org/PierreRambaud/groove-dl)
4
+
5
+ ##Requirements
6
+
7
+ * Ruby 1.9.3 or newer
8
+
9
+ ##Installation
10
+
11
+ From RubyGems
12
+
13
+ ```
14
+ $ gem install groove-dl
15
+ ```
16
+
17
+ From Github
18
+
19
+ ```
20
+ $ git clone https://github.com/PierreRambaud/groove-dl.git
21
+ $ cd groove-dl
22
+ $ bundle install
23
+ $ bundle exec rake install
24
+ ```
25
+
26
+ ## Usage
27
+
28
+ Run `groove-dl` and a gtk app will be displayed, you can:
29
+
30
+ * search for playlist or song
31
+ * choose download directory
32
+ * add them to queue
33
+ * download concurrently
34
+
35
+
36
+ In CLI mode:
37
+
38
+ ```bash
39
+ $ groove-dl-cli --help
40
+ Usage: groove-dl [COMMAND] [OPTIONS]
41
+
42
+ Options:
43
+
44
+ -v, --version Shows the current version
45
+ -p, --playlist Playlist
46
+ -s, --song Song
47
+ -o, --output Output directory
48
+ -h, --help Display this help message.
49
+
50
+ Available commands:
51
+
52
+ search Search for something on GrooveShark
53
+
54
+ See `<command> --help` for more information on a specific command.
55
+ ```
56
+
57
+ Search for song:
58
+ ```bash
59
+ $ groove-dl-cli search --help
60
+ Usage: groove-dl search [OPTIONS]
61
+
62
+ Options:
63
+
64
+ -p, --playlist Playlist
65
+ -s, --song Song
66
+ -h, --help Display this help message.
67
+ ```
68
+
69
+ ## Running tests
70
+
71
+ To run unit tests:
72
+ `$ bundle exec rake spec`
73
+
74
+ To check code style:
75
+ `$ bundle exec rake rubocop`
76
+
77
+ To run all tests:
78
+ `$ bundle exec rake`
79
+
80
+ ##Disclamer
81
+
82
+ You must have paid the song before download it, thus I'm not responsible for any violations this script does to Grooveshark's Terms Of Use.
83
+ This is just a project for learning how to create gtk app in ruby.
84
+
85
+
86
+ ## License
87
+ See [LICENSE](LICENSE) file
@@ -0,0 +1,10 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'bundler/gem_tasks'
3
+
4
+ GEMSPEC = Gem::Specification.load('gemirro.gemspec')
5
+
6
+ Dir['./task/*.rake'].each do |task|
7
+ import(task)
8
+ end
9
+
10
+ task default: [:rubocop, :spec]
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- coding: utf-8 -*-
3
+
4
+ require File.expand_path('../../lib/groove-dl', __FILE__)
5
+
6
+ require 'gtk3'
7
+ require 'groove-dl/app'
8
+ require 'groove-dl/widgets/download/bar'
9
+ require 'groove-dl/widgets/download/list/queue'
10
+ require 'groove-dl/widgets/download/list/failed'
11
+ require 'groove-dl/widgets/download/list/success'
12
+ require 'groove-dl/widgets/download/book'
13
+ require 'groove-dl/widgets/search/bar'
14
+ require 'groove-dl/widgets/search/list'
15
+
16
+ Gtk.init
17
+ GrooveDl::App.new
18
+ Gtk.main
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- coding: utf-8 -*-
3
+
4
+ require File.expand_path('../../lib/groove-dl', __FILE__)
5
+
6
+ require 'ruby-progressbar'
7
+ require 'slop'
8
+ require 'terminal-table'
9
+
10
+ require 'groove-dl/cli'
11
+ require 'groove-dl/cli/search'
12
+ require 'groove-dl/displayer'
13
+
14
+ options = GrooveDl::CLI.options
15
+ puts options if options.parse.empty?
@@ -0,0 +1,32 @@
1
+ # -*- coding: utf-8 -*-
2
+ require File.expand_path('../lib/groove-dl/version', __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'groove-dl'
6
+ s.version = GrooveDl::VERSION
7
+ s.authors = ['Pierre Rambaud']
8
+ s.email = 'pierre.rambaud86@gmail.com'
9
+ s.license = 'GPL-3.0'
10
+ s.summary = 'Download grooveshark songs.'
11
+ s.homepage = 'http://github.com/PierreRambaud/groove-dl'
12
+ s.description = 'Grooveshark downloader allow you to choose' \
13
+ 'songs and download them.'
14
+ s.executables = ['groove-dl', 'groove-dl-cli']
15
+
16
+ s.files = File.read(File.expand_path('../MANIFEST', __FILE__)).split("\n")
17
+
18
+ s.required_ruby_version = '>= 1.9.2'
19
+
20
+ s.add_dependency 'slop', '~>3.6'
21
+ s.add_dependency 'grooveshark', '~>0.2.12'
22
+ s.add_dependency 'ruby-progressbar', '~>1.7.0'
23
+ s.add_dependency 'terminal-table', '~>1.4.5'
24
+ s.add_dependency 'gtk3', '~>2.2'
25
+
26
+ s.add_development_dependency 'fakefs', '~>0.6.0'
27
+ s.add_development_dependency 'rake', '~>10.0'
28
+ s.add_development_dependency 'rack-test', '~>0.6'
29
+ s.add_development_dependency 'rspec', '~>3.0'
30
+ s.add_development_dependency 'simplecov', '~>0.9'
31
+ s.add_development_dependency 'rubocop', '~>0.25'
32
+ end
@@ -0,0 +1,13 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'logger'
4
+ require 'grooveshark'
5
+
6
+ unless $LOAD_PATH.include?(File.expand_path('../', __FILE__))
7
+ $LOAD_PATH.unshift(File.expand_path('../', __FILE__))
8
+ end
9
+
10
+ require 'groove-dl/configuration'
11
+ require 'groove-dl/downloader'
12
+ require 'groove-dl/version'
13
+ require 'groove-dl/errors'
@@ -0,0 +1,54 @@
1
+ # -*- coding: utf-8 -*-
2
+ module GrooveDl
3
+ # Bootstraper for the application
4
+ class App < Gtk::Window
5
+ def initialize
6
+ super
7
+
8
+ client = Grooveshark::Client.new
9
+ box = Gtk::Box.new(:vertical)
10
+
11
+ search_bar = Widgets::Search::Bar.new(:vertical, 6)
12
+ search_bar.load(client, self)
13
+
14
+ search_list = Widgets::Search::List.new(:vertical, 6)
15
+ search_list.load(client, self)
16
+
17
+ download_bar = Widgets::Download::Bar.new(:vertical, 6)
18
+ download_bar.load(client, self)
19
+
20
+ download_book = Widgets::Download::Book.new
21
+ download_book.load(client, self)
22
+
23
+ box.pack_start(search_bar, expand: false, fill: true, padding: 10)
24
+ box.pack_start(search_list, expand: true, fill: true, padding: 5)
25
+ box.pack_start(download_bar, expand: false, fill: true, padding: 10)
26
+ box.pack_start(download_book, expand: true, fill: true, padding: 5)
27
+
28
+ add(box)
29
+
30
+ init_default
31
+ end
32
+
33
+ def init_default
34
+ signal_connect('destroy') do
35
+ Gtk.main_quit
36
+ end
37
+
38
+ set_title('Grooveshark Downloader')
39
+ set_default_size(1024, 768)
40
+ set_window_position(:center)
41
+ show_all
42
+ end
43
+
44
+ def find_by_name(element = self, name)
45
+ return element if element.name == name
46
+ element.children.each do |child|
47
+ result = find_by_name(child, name)
48
+ return result unless result.nil?
49
+ end if element.respond_to?(:children)
50
+
51
+ nil
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,58 @@
1
+ # -*- coding: utf-8 -*-
2
+ module GrooveDl
3
+ # CLI module
4
+ module CLI
5
+ ##
6
+ # Hash containing the default Slop options.
7
+ #
8
+ # @return [Hash]
9
+ #
10
+ SLOP_OPTIONS = {
11
+ strict: true,
12
+ help: true,
13
+ banner: 'Usage: groove-dl [COMMAND] [OPTIONS]'
14
+ }
15
+
16
+ ##
17
+ # @return [Slop]
18
+ #
19
+ def self.options
20
+ @options ||= default_options
21
+ end
22
+
23
+ ##
24
+ # @return [Slop]
25
+ #
26
+ def self.default_options
27
+ Slop.new(SLOP_OPTIONS.dup) do
28
+ separator "\nOptions:\n"
29
+
30
+ on :v, :version, 'Shows the current version' do
31
+ puts CLI.version_information
32
+ end
33
+
34
+ on :p=, :playlist=, 'Playlist', as: Integer
35
+ on :s=, :song=, 'Song', as: Integer
36
+ on :o=, :output=, 'Output directory', as: String
37
+
38
+ run do |opts|
39
+ next if opts[:v]
40
+
41
+ client = Grooveshark::Client.new
42
+ d = Downloader.new(client, opts)
43
+ d.playlist(opts[:p]) if opts[:p]
44
+ d.song(opts[:s]) if opts[:s]
45
+ end
46
+ end
47
+ end
48
+
49
+ ##
50
+ # Returns a String containing some platform/version related information.
51
+ #
52
+ # @return [String]
53
+ #
54
+ def self.version_information
55
+ "Groove-dl v#{VERSION} on #{RUBY_DESCRIPTION}"
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,30 @@
1
+ # -*- coding: utf-8 -*-
2
+ GrooveDl::CLI.options.command 'search' do
3
+ banner 'Usage: groove-dl search [OPTIONS]'
4
+ description 'Search for something on GrooveShark'
5
+ separator "\nOptions:\n"
6
+
7
+ on :p=, :playlist=, 'Playlist', as: String
8
+ on :s=, :song=, 'Song', as: String
9
+
10
+ run do |opts|
11
+ next if opts[:p].nil? && opts[:a].nil? && opts[:s].nil?
12
+ client = Grooveshark::Client.new
13
+
14
+ type = 'Songs' if opts[:s]
15
+ type = 'Playlists' if opts[:p]
16
+ query = opts[:s] if opts[:s]
17
+ query = opts[:p] if opts[:p]
18
+
19
+ results = client.request('getResultsFromSearch',
20
+ type: type,
21
+ query: query)['result']
22
+ results.map! do |data|
23
+ next Grooveshark::Song.new data if type == 'Songs'
24
+ data
25
+ end
26
+
27
+ displayer = GrooveDl::Displayer.new(results, type)
28
+ displayer.render
29
+ end
30
+ end
@@ -0,0 +1,14 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Groove Dl module
3
+ module GrooveDl
4
+ def self.configuration
5
+ @configuration ||= Configuration.new
6
+ end
7
+
8
+ # Configuration class
9
+ class Configuration
10
+ def logger
11
+ @logger ||= Logger.new(STDOUT)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,56 @@
1
+ # -*- coding: utf-8 -*-
2
+ module GrooveDl
3
+ # Downloader Class
4
+ class Displayer
5
+ attr_reader :result, :type
6
+
7
+ ##
8
+ # Initialize Displayer
9
+ #
10
+ # @params [Array] result The result from the search
11
+ # @params [String] type The search type
12
+ #
13
+ # @return [Nil]
14
+ #
15
+ def initialize(result, type)
16
+ @result = result
17
+ @type = type
18
+ end
19
+
20
+ ##
21
+ # Display prompt to choose songs or playlists.
22
+ #
23
+ def render
24
+ table = Terminal::Table.new(headings: headers, title: @type)
25
+ @result.each do |data|
26
+ add_row(table, data)
27
+ end
28
+
29
+ puts table.to_s
30
+ end
31
+
32
+ def headers
33
+ return %w(Id Album Artist Song) if @type == 'Songs'
34
+ return %w(Id Nam Author NumSongs) if @type == 'Playlists'
35
+ end
36
+
37
+ ##
38
+ # Add row into table
39
+ #
40
+ # @params [Terminal::Table] table Table in which row will be added
41
+ # @params [Array] result The result from the search
42
+ #
43
+ # @return [Nil]
44
+ #
45
+ def add_row(table, data)
46
+ table.add_row([data['playlist_id'],
47
+ data['name'],
48
+ data['f_name'],
49
+ data['num_songs']]) if @type == 'Playlists'
50
+ table.add_row([data.id,
51
+ data.album,
52
+ data.artist,
53
+ data.name]) if @type == 'Songs'
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,178 @@
1
+ # -*- coding: utf-8 -*-
2
+ module GrooveDl
3
+ # Downloader Class
4
+ class Downloader
5
+ attr_accessor :type
6
+ attr_writer :client
7
+ attr_writer :download_queue
8
+ attr_writer :download_count
9
+ attr_writer :download_skip
10
+
11
+ ##
12
+ # Initialize download
13
+ #
14
+ # @return [Downloader]
15
+ #
16
+ def initialize(client, options = {})
17
+ @client = client
18
+ @output_directory = options[:o] || Dir.tmpdir
19
+ @queue = []
20
+ @count = 0
21
+ @skip = 0
22
+ @type = 'cli'
23
+ end
24
+
25
+ ##
26
+ # Download song
27
+ #
28
+ # @params [String] song_id Song id
29
+ #
30
+ # @return [Array]
31
+ #
32
+ def song(song_id)
33
+ @queue << Grooveshark::Song.new('song_id' => song_id,
34
+ 'artist_name' => 'unknown',
35
+ 'song_name' => 'unknown')
36
+ download_queue
37
+ end
38
+
39
+ ##
40
+ # Download playlist
41
+ #
42
+ # @params [String] playlist_id Playlist id
43
+ #
44
+ # @return [Array]
45
+ #
46
+ def playlist(playlist_id)
47
+ playlist = Grooveshark::Playlist.new(@client,
48
+ 'playlist_id' => playlist_id)
49
+ songs = playlist.load_songs
50
+ return false unless songs
51
+ @queue += songs
52
+
53
+ download_queue
54
+ end
55
+
56
+ ##
57
+ # Download song
58
+ #
59
+ # @param [Grooveshark::Song] song Song object
60
+ # @param [Gtk::TreeIter/String] callback Proc
61
+ # function to execute during download
62
+ #
63
+ # @return [Net::HTTP]
64
+ #
65
+ def download(song, object)
66
+ url = URI.parse(@client.get_song_url_by_id(song.id))
67
+ @client.get_stream_auth_by_songid(song.id)
68
+
69
+ callback = process_gui_response(object) if @type == 'gui'
70
+ callback = process_cli_response(object) if @type == 'cli'
71
+
72
+ RestClient::Request
73
+ .execute(method: :get,
74
+ url: url.to_s,
75
+ block_response: callback)
76
+ end
77
+
78
+ ##
79
+ # Download queue
80
+ #
81
+ # @return [false|Array]
82
+ #
83
+ def download_queue
84
+ return false if @queue.empty?
85
+ @queue.each do |song|
86
+ download(song, build_path(@output_directory, song))
87
+ end
88
+
89
+ { skipped: @skip, downloaded: @count }
90
+ end
91
+
92
+ ##
93
+ # Build path
94
+ #
95
+ # @param [String] output_directory destination directory
96
+ # @param [Grooveshark::Song] song Song
97
+ #
98
+ def build_path(output_directory, song)
99
+ sprintf('%s/%s/%s/%s.mp3',
100
+ output_directory,
101
+ song.artist,
102
+ song.album,
103
+ song.name)
104
+ end
105
+
106
+ ##
107
+ # Process response to display a progress bar and download
108
+ # file into destination.
109
+ #
110
+ # @param [String] destination Destination
111
+ #
112
+ # @return [Proc]
113
+ #
114
+ def process_cli_response(destination)
115
+ proc do |response|
116
+ total_size = response['content-length'].to_i
117
+ if File.exist?(destination) &&
118
+ File.size?(destination) == total_size
119
+
120
+ @skip += 1
121
+ fail Errors::AlreadyDownloaded, "#{destination} already downloaded"
122
+ end
123
+
124
+ pbar = ProgressBar.create(title: destination.split('/').last,
125
+ format: '%a |%b>>%i| %p%% %t',
126
+ total: response['content-length'].to_i)
127
+ File.open(destination, 'w') do |f|
128
+ response.read_body do |chunk|
129
+ f.write(chunk)
130
+ pbar.progress += chunk.length
131
+ end
132
+ end
133
+
134
+ pbar.finish
135
+ @count += 1
136
+ end
137
+ end
138
+
139
+ ##
140
+ # Process response to pulse progressbar stored in the
141
+ # TreeIter object.
142
+ #
143
+ # @param [Gtk::TreeIter] object TreeIter
144
+ #
145
+ # @return [Proc]
146
+ #
147
+ def process_gui_response(object)
148
+ proc do |response|
149
+ pgbar_value = Widgets::Download::List::Queue::COLUMN_PGBAR_VALUE
150
+ pgbar_text = Widgets::Download::List::Queue::COLUMN_PGBAR_TEXT
151
+
152
+ total_size = response['content-length'].to_i
153
+ path = object[Widgets::Download::List::Queue::COLUMN_PATH]
154
+ if File.exist?(path) &&
155
+ File.size?(path) == total_size
156
+ object[pgbar_value] = 100
157
+ object[pgbar_text] = 'Complete'
158
+ fail Errors::AlreadyDownloaded, "#{path} already downloaded"
159
+ end
160
+
161
+ dirname = File.dirname(path)
162
+ FileUtils.mkdir_p(dirname) unless File.directory?(dirname)
163
+
164
+ File.open(path, 'w') do |f|
165
+ file_size = 0
166
+ response.read_body do |chunk|
167
+ f.write(chunk)
168
+ file_size += chunk.length
169
+ result = ((file_size * 100) / total_size).to_i
170
+ object[pgbar_value] = result
171
+ object[pgbar_text] = 'Complete' if
172
+ object[pgbar_value] >= 100
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end