groove-dl 0.1.0

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,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