cultome_player 2.0.0 → 2.0.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.
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