cultome_player 2.0.0 → 2.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +24 -79
  3. data/Rakefile +38 -4
  4. data/bin/cultome_player +16 -4
  5. data/config/environment.yml +4 -0
  6. data/cultome_player.gemspec +8 -1
  7. data/lib/cultome_player.rb +45 -19
  8. data/lib/cultome_player/command/language.rb +3 -4
  9. data/lib/cultome_player/command/processor.rb +9 -7
  10. data/lib/cultome_player/command/reader.rb +11 -2
  11. data/lib/cultome_player/environment.rb +21 -13
  12. data/lib/cultome_player/events.rb +7 -7
  13. data/lib/cultome_player/objects/album.rb +9 -2
  14. data/lib/cultome_player/objects/artist.rb +9 -2
  15. data/lib/cultome_player/objects/command.rb +10 -1
  16. data/lib/cultome_player/objects/drive.rb +4 -1
  17. data/lib/cultome_player/objects/genre.rb +4 -1
  18. data/lib/cultome_player/objects/parameter.rb +21 -1
  19. data/lib/cultome_player/objects/response.rb +5 -1
  20. data/lib/cultome_player/objects/song.rb +9 -4
  21. data/lib/cultome_player/player/adapter/mpg123.rb +3 -4
  22. data/lib/cultome_player/player/interactive.rb +67 -11
  23. data/lib/cultome_player/player/interface/basic.rb +13 -9
  24. data/lib/cultome_player/player/interface/extended.rb +26 -12
  25. data/lib/cultome_player/player/interface/helper.rb +73 -9
  26. data/lib/cultome_player/plugins.rb +44 -18
  27. data/lib/cultome_player/plugins/alias.rb +85 -0
  28. data/lib/cultome_player/plugins/gestures.rb +90 -0
  29. data/lib/cultome_player/plugins/help.rb +4 -4
  30. data/lib/cultome_player/plugins/keyboard_special_keys.rb +22 -0
  31. data/lib/cultome_player/plugins/points.rb +51 -0
  32. data/lib/cultome_player/utils.rb +117 -7
  33. data/lib/cultome_player/version.rb +1 -1
  34. data/xbindkeys/xbindkeysrc +12 -0
  35. metadata +37 -89
  36. data/.coveralls.yml +0 -1
  37. data/.gitignore +0 -24
  38. data/.rspec +0 -2
  39. data/.travis.yml +0 -7
  40. data/db/001_create_schema.rb +0 -58
  41. data/spec/config.yml +0 -0
  42. data/spec/cultome_player/command/processor_spec.rb +0 -168
  43. data/spec/cultome_player/command/reader_spec.rb +0 -45
  44. data/spec/cultome_player/cultome_player_spec.rb +0 -17
  45. data/spec/cultome_player/environment_spec.rb +0 -65
  46. data/spec/cultome_player/events_spec.rb +0 -22
  47. data/spec/cultome_player/media_spec.rb +0 -41
  48. data/spec/cultome_player/player/adapter/mpg123_spec.rb +0 -82
  49. data/spec/cultome_player/player/interface/basic_spec.rb +0 -168
  50. data/spec/cultome_player/player/interface/extended/connect_spec.rb +0 -117
  51. data/spec/cultome_player/player/interface/extended/search_spec.rb +0 -90
  52. data/spec/cultome_player/player/interface/extended/show_spec.rb +0 -36
  53. data/spec/cultome_player/player/interface/extended/shuffle_spec.rb +0 -26
  54. data/spec/cultome_player/player/interface/extended_spec.rb +0 -136
  55. data/spec/cultome_player/player/interface/helper_spec.rb +0 -63
  56. data/spec/cultome_player/player/interface_spec.rb +0 -17
  57. data/spec/cultome_player/player/playlist_spec.rb +0 -301
  58. data/spec/cultome_player/plugins/help_spec.rb +0 -21
  59. data/spec/cultome_player/plugins_spec.rb +0 -19
  60. data/spec/cultome_player/utils_spec.rb +0 -15
  61. data/spec/spec_helper.rb +0 -108
  62. data/spec/test/uno/dos/dos.mp3 +0 -0
  63. data/spec/test/uno/dos/tres/tres.mp3 +0 -0
  64. data/spec/test/uno/uno.mp3 +0 -0
  65. data/tasks/console.rake +0 -19
  66. data/tasks/db.rake +0 -19
  67. data/tasks/run.rake +0 -7
@@ -23,8 +23,9 @@ module CultomePlayer
23
23
  songs = whole_library
24
24
  return failure("No music connected! You should try 'connect /home/yoo/music => main' first") if songs.empty?
25
25
  playlists[:current, :focus] <= songs
26
+ playlists[:current, :focus].shuffle
26
27
 
27
- else
28
+ else # with parameters
28
29
  songs = select_songs_with cmd
29
30
  # checamos si el tipo de comando es para programar una
30
31
  # nueva playlist o solo para tocar una cancion
@@ -34,8 +35,8 @@ module CultomePlayer
34
35
  playlists[:current] <= songs
35
36
  end
36
37
  end
37
-
38
- return success(playlist: songs) + execute("next no_history")
38
+
39
+ return success(playlist: songs) + execute("next no_history").first
39
40
  end
40
41
 
41
42
  # For more information on this command refer to user manual or inline help in interactive mode.
@@ -63,16 +64,18 @@ module CultomePlayer
63
64
 
64
65
  # For more information on this command refer to user manual or inline help in interactive mode.
65
66
  def next(cmd)
67
+ playlists[:history] << current_song if cmd.history?
68
+
66
69
  if playlists[:queue].empty?
67
- unless cmd.params(:literal).any?{|p| p.value == 'no_history'}
68
- playlists[:history] << current_song
69
- end
70
70
  playlists[:queue] << playlists[:current].next
71
71
  end
72
72
 
73
73
  # aqui enviamos al reproductor externo a tocar
74
74
  play_queue
75
75
 
76
+ # cambiamos la fecha de ultima reproduccion
77
+ current_song.update_attributes(last_played_at: Time.now)
78
+
76
79
  return success(message: "Now playing #{current_song}", now_playing: current_song)
77
80
  end
78
81
 
@@ -80,15 +83,16 @@ module CultomePlayer
80
83
  def prev(cmd)
81
84
  playlists[:queue] << playlists[:history].pop
82
85
  playlists[:current].rewind_by 1
83
- execute("next no_history")
86
+ r = execute("next no_history").first
87
+ return r
84
88
  end
85
89
 
86
90
  # For more information on this command refer to user manual or inline help in interactive mode.
87
91
  def quit(cmd)
88
92
  quit_in_player
89
93
  terminate_session
90
- return success("See you next time!") unless in_session?
91
- return failure("Oops! You should use Ctr-c or throw water to the CPU NOW!!!!")
94
+ return success(c15("See you next time!")) unless in_session?
95
+ return failure(c3("Oops! You should use Ctr-c or throw water to the CPU NOW!!!!"))
92
96
  end
93
97
  end
94
98
  end
@@ -1,7 +1,11 @@
1
+ require 'cultome_player/player/interface/helper'
1
2
 
2
3
  module CultomePlayer::Player::Interface
3
4
  module Extended
4
5
 
6
+ include CultomePlayer::Objects
7
+ include CultomePlayer::Player::Interface::Helper
8
+
5
9
  # For more information on this command refer to user manual or inline help in interactive mode.
6
10
  def search(cmd)
7
11
  songs = select_songs_with cmd
@@ -10,6 +14,7 @@ module CultomePlayer::Player::Interface
10
14
  failure('It matches not even one')
11
15
  else
12
16
  playlists[:focus] <= songs
17
+ playlists[:search] <= songs
13
18
  success(songs: songs, response_type: :songs)
14
19
  end
15
20
  end
@@ -19,8 +24,8 @@ module CultomePlayer::Player::Interface
19
24
  if cmd.params.empty?
20
25
  if playing?
21
26
  #mostramos la cancion actual
22
- msg = get_progress_bar_with_labels(playback_position, playback_length, 20, format_secs(playback_position), format_secs(playback_length))
23
- return success(message: "#{current_song.to_s}\n#{msg}", song: current_song)
27
+ progress_bar = get_progress_bar_with_labels(playback_position, playback_length, 20, format_secs(playback_position), format_secs(playback_length))
28
+ return success(message: "#{current_song.to_s}\n#{c7(progress_bar)}", song: current_song)
24
29
  else
25
30
  return failure("Nothing to show yet. Try with 'play' first.")
26
31
  end
@@ -37,22 +42,30 @@ module CultomePlayer::Player::Interface
37
42
  when :search then playlists[:search].to_a
38
43
 
39
44
  when :song then return success(message: current_song.to_s, song: current_song)
40
- when :artist then return success(message: current_artist.to_s, artist: current_song)
41
- when :album then return success(message: current_album.to_s, album: current_song)
45
+ when :artist then return success(message: current_artist.to_s, artist: current_artist)
46
+ when :album then return success(message: current_album.to_s, album: current_album)
47
+ when :genre then return success(message: current_song.genres.map{|g| g.name}.join(", "), genres: current_song.genres)
42
48
 
43
49
  when :drives then Drive.all
44
- when :artists then return Artist.all
45
- when :albums then return Album.all
46
- when :genres then return Genre.all
50
+ when :artists then Artist.all
51
+ when :albums then Album.all
52
+ when :genres then Genre.all
47
53
 
48
54
  when :library then whole_library.to_a
49
55
 
50
- when :recently_added then [] # TODO implement
51
- when :recently_played then [] # TODO implement
52
- when :more_played then [] # TODO implement
53
- when :less_played then [] # TODO implement
54
- when :populars then [] # TODO implement
56
+ when :recently_added then
57
+ low_time, high_time = get_recently_added_criteria_limit
58
+ Song.where({created_at: low_time..high_time}).to_a
59
+
60
+ when :recently_played then
61
+ low_time, high_time = get_recently_played_criteria_limit
62
+ Song.where({last_played_at: low_time..high_time}).to_a
55
63
 
64
+ when :most_played then Song.where("plays >= ?", get_most_played_criteria_limit).to_a
65
+ when :less_played then Song.where("plays <= ?", get_less_played_criteria_limit).to_a
66
+ when :populars then
67
+ low_time, up_time, low_count = get_popular_criteria_limits
68
+ Song.where("last_played_at between ? and ? and plays >= ?", low_time, up_time, low_count)
56
69
  else []
57
70
  end
58
71
  end
@@ -137,6 +150,7 @@ module CultomePlayer::Player::Interface
137
150
  # insertamos las nuevas y actualizamos las existentes
138
151
  updated = update_song(track_info)
139
152
  imported = insert_song(track_info)
153
+ display "" # para insertar un salto de linea despues de las barras de progreso
140
154
 
141
155
  success(message: connect_response_msg(imported, updated),
142
156
  files_detected: track_info.size,
@@ -32,9 +32,9 @@ module CultomePlayer::Player::Interface
32
32
  def get_progress_bar(current, total=100, size=10)
33
33
  factor = total > 0 ? current / total.to_f : 0
34
34
  bars = ( factor * size ).floor
35
- total = "_" * size
35
+ total = "-" * size
36
36
  total[0,bars] = "#" * bars
37
- return "|#{total}|"
37
+ return "|#{total}>"
38
38
  end
39
39
 
40
40
  # Generate a query and a array of values to replace into the query for a given set of parameters.
@@ -49,8 +49,6 @@ module CultomePlayer::Player::Interface
49
49
  when :criteria then process_criteria_for_search(params)
50
50
  when :literal then process_literal_for_search(params)
51
51
  end
52
-
53
- raise 'invalid command:invalid search criteria' if query =~ /^[\(\)]+$/
54
52
  return query, values
55
53
  end
56
54
 
@@ -72,7 +70,9 @@ module CultomePlayer::Player::Interface
72
70
  def insert_song(new_info)
73
71
  existing_paths = get_unique_paths
74
72
  to_be_processed = new_info.select{|s| !existing_paths.include?(s[:file_path]) }
73
+ progress = progress_label("Adding songs {actual}/{total}", to_be_processed.size)
75
74
  return to_be_processed.count do |info|
75
+ display_over c15(progress.increment)
76
76
  write_song(info)
77
77
  end
78
78
  end
@@ -84,8 +84,10 @@ module CultomePlayer::Player::Interface
84
84
  def update_song(new_info)
85
85
  existing_paths = get_unique_paths
86
86
  to_be_processed = new_info.select{|s| existing_paths.include?(s[:file_path]) }
87
+ progress = progress_label("Updating song {actual} of {total}", to_be_processed.size)
87
88
  return to_be_processed.count do |info|
88
89
  # extraemos la cancion almacenada.. si existe
90
+ display_over c15(progress.increment)
89
91
  song = Song.includes(:drive).where("drives.path||'/'||songs.relative_path = ?", info[:file_path]).references(:drives).first
90
92
 
91
93
  song.nil? ? false : write_song(info, song)
@@ -117,7 +119,7 @@ module CultomePlayer::Player::Interface
117
119
  from_focus = get_from_focus(cmd.params(:number))
118
120
  from_playlists = get_from_playlists(cmd.params_values(:object))
119
121
  results = found_songs + from_focus + from_playlists
120
- return results
122
+ return results.uniq{|s| s.id }
121
123
  end
122
124
 
123
125
  # Search in library for songs that fullfil the command parameters.
@@ -141,7 +143,8 @@ module CultomePlayer::Player::Interface
141
143
  # @return [List<Song>] The songs in the valid playlists.
142
144
  def get_from_playlists(lists)
143
145
  valid_lists = lists.select{|list_name| playlist?(list_name) }
144
- return playlists[*valid_lists].songs
146
+ songs = playlists[*valid_lists].songs
147
+ return songs.uniq{|s| s.id }
145
148
  end
146
149
 
147
150
  # Try to find the player object by name.
@@ -191,6 +194,19 @@ module CultomePlayer::Player::Interface
191
194
 
192
195
  private
193
196
 
197
+ def progress_label(label, total)
198
+ return Class.new do
199
+ def initialize(lbl, tot)
200
+ @_label, @_actual = lbl.gsub(/\{total\}/, tot.to_s), 0
201
+ end
202
+
203
+ def increment
204
+ @_actual += 1
205
+ return @_label.gsub(/\{actual\}/, @_actual.to_s)
206
+ end
207
+ end.new(label, total)
208
+ end
209
+
194
210
  def get_from_focus(params)
195
211
  params.map do |p|
196
212
  playlists[:focus].at p.value - 1
@@ -258,10 +274,26 @@ module CultomePlayer::Player::Interface
258
274
  def process_object_for_search(params)
259
275
  objs = params.collect do |p|
260
276
  case p.value
261
- when :artist then {query: 'artists.name = ?', value: current_artist.name }
277
+ when :populars then
278
+ low_time, up_time, low_count = get_popular_criteria_limits
279
+ {query: 'last_played_at between ? and ? and plays >= ?', value: [low_time, up_time, low_count]}
280
+ when :less_played then {query: 'plays <= ?', value: get_less_played_criteria_limit}
281
+ when :most_played then {query: 'plays >= ?', value: get_most_played_criteria_limit}
282
+ when :recently_played then
283
+ low_time, high_time = get_recently_played_criteria_limit
284
+ {query: 'last_played_at between ? and ?', value: [low_time, high_time]}
285
+ when :recently_added then
286
+ low_time, high_time = get_recently_added_criteria_limit
287
+ {query: 'created_at between ? and ?', value: [low_time, high_time]}
288
+ when :library then {query: 'songs.id > 0'}
262
289
  when :album then {query: 'albums.name = ?', value: current_album.name }
290
+ when :artist then {query: 'artists.name = ?', value: current_artist.name }
263
291
  when :song then {query: 'songs.name = ?', value: current_song.name }
264
- when :library then {query: 'songs.id > 0'}
292
+ when :genre then {query: 'genres.name in (?)', value: current_song.genres.map{|g| g.name} }
293
+ when :genres then raise 'invalid_search:@genres has no meaning here'
294
+ when :albums then raise 'invalid_search:@albums has no meaning here'
295
+ when :artists then raise 'invalid_search:@artists has no meaning here'
296
+ when :drives then raise 'invalid_search:@drives has no meaning here'
265
297
  else raise 'invalid search:unknown type'
266
298
  end unless playlist?(p.value)
267
299
  end.compact
@@ -269,7 +301,7 @@ module CultomePlayer::Player::Interface
269
301
  return nil, [] if objs.empty?
270
302
 
271
303
  query = objs.collect{|o| o[:query] }.join(" or ")
272
- values = objs.collect{|o| o[:value]}.compact
304
+ values = objs.collect{|o| o[:value]}.flatten.compact
273
305
 
274
306
  return query, values
275
307
  end
@@ -286,6 +318,7 @@ module CultomePlayer::Player::Interface
286
318
  when :a then info[:query] << "artists.name like ?"
287
319
  when :b then info[:query] << "albums.name like ?"
288
320
  when :t then info[:query] << "songs.name like ?"
321
+ else raise 'invalid command:invalid search criteria'
289
322
  end
290
323
  info[:values] << "%#{p.value}%"
291
324
  end
@@ -295,6 +328,37 @@ module CultomePlayer::Player::Interface
295
328
  return query, values
296
329
  end
297
330
 
331
+ # Get the low and high date range and the lower play limit for a "popular" songs criteria
332
+ def get_popular_criteria_limits
333
+ upper_time = Time.now.midnight + 1.day
334
+ lower_time = upper_time - 5.day
335
+ latest_songs = Song.where({last_played_at: (lower_time..upper_time)}).order(plays: :desc)
336
+ lower_play_count = latest_songs.first.plays * 0-95
337
+
338
+ return lower_time, upper_time, lower_play_count
339
+ end
340
+
341
+ # Get the high limit for a "less played" songs criteria
342
+ def get_less_played_criteria_limit
343
+ less_played_song_count = Song.all.order(:plays).limit(1).first.plays
344
+ return less_played_song_count * 1.05
345
+ end
346
+
347
+ # Get the lower limit for a "most played" songs criteria
348
+ def get_most_played_criteria_limit
349
+ most_played_song_count = Song.all.order(plays: :desc).limit(1).first.plays
350
+ return most_played_song_count * 0.95
351
+ end
352
+
353
+ def get_recently_added_criteria_limit
354
+ last_song_added_dt = Song.all.order(created_at: :desc).limit(1).first.created_at
355
+ return last_song_added_dt - 1.day, last_song_added_dt
356
+ end
357
+
358
+ def get_recently_played_criteria_limit
359
+ last_song_played_dt = Song.all.order(last_played_at: :desc).limit(1).first.last_played_at
360
+ return last_song_played_dt - 1.hour, last_song_played_dt
361
+ end
298
362
  end
299
363
  end
300
364
 
@@ -1,23 +1,49 @@
1
1
  require 'cultome_player/plugins/help'
2
+ require 'cultome_player/plugins/alias'
3
+ require 'cultome_player/plugins/points'
4
+ require 'cultome_player/plugins/gestures'
5
+ require 'cultome_player/plugins/keyboard_special_keys'
2
6
 
3
7
  module CultomePlayer
4
- module Plugins
5
- include Help
8
+ module Plugins
9
+ include Help
10
+ include Alias
11
+ include Points
12
+ include Gestures
13
+ include KeyboardSpecialKeys
6
14
 
7
- # Check if a plugin implements the given command.
8
- #
9
- # @param cmd_name [String] The command name.
10
- # @return [Boolean] True is the given command is implemented by a plugin.
11
- def plugins_respond_to?(cmd_name)
12
- return respond_to?("command_#{cmd_name}".to_sym)
13
- end
15
+ # Check if a plugin implements the given command.
16
+ #
17
+ # @param cmd_name [String] The command name.
18
+ # @return [Boolean] True is the given command is implemented by a plugin.
19
+ def plugins_respond_to?(cmd_name)
20
+ return respond_to?("command_#{cmd_name}".to_sym)
21
+ end
14
22
 
15
- # Get a command format for a command implemented by a plugin
16
- #
17
- # @param cmd_name [String] The command name.
18
- # @return [Regex] The regex to validate a command format that is implemented by a plugin.
19
- def plugin_command_sintaxis(cmd_name)
20
- return send("sintaxis_#{cmd_name}".to_sym)
21
- end
22
- end
23
- end
23
+ # Get a command format for a command implemented by a plugin
24
+ #
25
+ # @param cmd_name [String] The command name.
26
+ # @return [Regex] The regex to validate a command format that is implemented by a plugin.
27
+ def plugin_command_sintax(cmd_name)
28
+ return send("sintax_#{cmd_name}".to_sym)
29
+ end
30
+
31
+ # Lazy getter for plugins configurator. Its a persistent store where plugin can put their configurations.
32
+ #
33
+ # @param plugin_name [#to_s] The name of the plugin.
34
+ # @return [Hash] Where plugins can stores their information.
35
+ def plugin_config(plugin_name)
36
+ plugin_ns = player_config['plugins'] ||= {}
37
+ return plugin_ns[plugin_name.to_s] ||= {}
38
+ end
39
+
40
+ # Call init_plugin_<action> to initialize all the plugins that require it.
41
+ def init_plugins
42
+ methods.grep(/^init_plugin_/).each{|method_name| send(method_name) }
43
+ end
44
+
45
+ def clean_plugins
46
+ methods.grep(/^clean_plugin_/).each{|method_name| send(method_name) }
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,85 @@
1
+ module CultomePlayer
2
+ module Plugins
3
+ module Alias
4
+
5
+ def command_alias(cmd)
6
+ if cmd.params.empty?
7
+ msg = plugin_config(:alias).to_a.reduce("") do |acc,arr|
8
+ acc += "#{arr[0]} => #{arr[1]}\n"
9
+ end
10
+ return success(c15(msg))
11
+
12
+ else
13
+ command = cmd.params.first.value
14
+ aka = cmd.params.last.value
15
+ plugin_config(:alias)[aka] = command
16
+ return success("Alias '#{aka}' created!")
17
+ end
18
+ end
19
+
20
+ def respond_to?(name, include_all=false)
21
+ # si no esta buscando commands lo dejamos pasar
22
+ return super unless name =~ /^command_(.+?)$/
23
+ # si el comando existe, lo dejamos pasar
24
+ return true if super
25
+ # aqui podria ser un alias
26
+ aka = $1
27
+ cmd_aliased = plugin_config(:alias)[aka]
28
+ # si no es un alias, lo dejamos pasar
29
+ return false if cmd_aliased.nil?
30
+ # aqui definimos los metodos requeridos para el alias
31
+ # alias execution...
32
+ self.class.send(:define_method, "command_#{aka}".to_sym) do |cmd|
33
+ prepared_command = cmd_aliased.clone
34
+ # we replace parameters
35
+ cmd.params_values(:literal).each.with_index do |p, idx|
36
+ prepared_command.gsub!(/%#{idx + 1}/, "'#{p}'") # TODO aqui manejar los parametros con quotes
37
+ end
38
+ # execute the alias, and returns only the last response
39
+ if in_session?
40
+ begin
41
+ r = execute_interactively(prepared_command)
42
+ return success(no_response: true)
43
+ #return success(no_response: true)
44
+ rescue Exception => e
45
+ return failure(e.message)
46
+ end
47
+ end
48
+ return execute(prepared_command).last
49
+ end
50
+ # ...and sintax
51
+ self.class.send(:define_method, "sintax_#{aka}".to_sym) do
52
+ return /^literal(literal|[\s]+)*$/ # devolvemos la sintax para la invocacion del alias, no del comando
53
+ end
54
+ ## devolvemos exito
55
+ return true
56
+ end
57
+
58
+ def sintax_alias
59
+ /^literal (literal) bubble (literal) $/
60
+ end
61
+
62
+ def description_alias
63
+ "Create an alias for a command."
64
+ end
65
+
66
+ def usage_alias
67
+ return <<-USAGE
68
+ usage: alias command => alias
69
+
70
+ Creates an alias for a command. Similar to what the bash do.
71
+ Can receive parameters in form of %<number>, eg %1, %2, %3... These parameters are passed when the alias is called.
72
+
73
+ Examples:
74
+
75
+ Create an alias to always play the first result in a search:
76
+ alias "search %1 && play 1" => s
77
+
78
+ And you would call it like
79
+ s metallica
80
+
81
+ USAGE
82
+ end
83
+ end
84
+ end
85
+ end