hearken 0.1.2 → 0.1.3

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 (59) hide show
  1. checksums.yaml +5 -5
  2. data/.rubocop.yml +14 -0
  3. data/.tool-versions +1 -0
  4. data/Gemfile +7 -1
  5. data/Gemfile.lock +95 -0
  6. data/{README.rdoc → README.md} +12 -50
  7. data/bin/console +15 -0
  8. data/bin/setup +8 -0
  9. data/exe/hearken +9 -0
  10. data/exe/hearken_index +13 -0
  11. data/hearken.gemspec +35 -35
  12. data/lib/hearken/command/enqueue.rb +35 -10
  13. data/lib/hearken/command/reload.rb +18 -6
  14. data/lib/hearken/command/search.rb +31 -20
  15. data/lib/hearken/console.rb +11 -28
  16. data/lib/hearken/debug.rb +6 -6
  17. data/lib/hearken/indexing/indexer.rb +32 -24
  18. data/lib/hearken/library.rb +49 -41
  19. data/lib/hearken/monkey_violence.rb +9 -7
  20. data/lib/hearken/paths.rb +13 -19
  21. data/lib/hearken/range_expander.rb +18 -13
  22. data/lib/hearken/tagged.rb +15 -11
  23. data/lib/hearken/track.rb +53 -41
  24. data/lib/hearken.rb +2 -2
  25. metadata +26 -105
  26. data/.gitignore +0 -5
  27. data/HISTORY.rdoc +0 -38
  28. data/MIT-LICENSE +0 -20
  29. data/bin/hearken +0 -7
  30. data/bin/hearken_index +0 -12
  31. data/bin/hearken_scrobble +0 -35
  32. data/bin/hearken_tags +0 -11
  33. data/lib/hearken/command/list.rb +0 -32
  34. data/lib/hearken/command/love.rb +0 -7
  35. data/lib/hearken/command/profile.rb +0 -7
  36. data/lib/hearken/command/recent.rb +0 -38
  37. data/lib/hearken/command/remove.rb +0 -15
  38. data/lib/hearken/command/restart.rb +0 -7
  39. data/lib/hearken/command/scrobbling.rb +0 -14
  40. data/lib/hearken/command/setup_scrobbling.rb +0 -7
  41. data/lib/hearken/command/shuffle.rb +0 -13
  42. data/lib/hearken/command/start.rb +0 -7
  43. data/lib/hearken/command/status.rb +0 -7
  44. data/lib/hearken/command/stop.rb +0 -7
  45. data/lib/hearken/command.rb +0 -35
  46. data/lib/hearken/notification/growl_notifier.rb +0 -15
  47. data/lib/hearken/player.rb +0 -130
  48. data/lib/hearken/preferences.rb +0 -33
  49. data/lib/hearken/queue.rb +0 -33
  50. data/lib/hearken/scrobbler.rb +0 -82
  51. data/lib/hearken/simple_scrobbler.rb +0 -94
  52. data/media/ice_cream.png +0 -0
  53. data/spec/hearken/command/enqueue_spec.rb +0 -24
  54. data/spec/hearken/command/list_spec.rb +0 -31
  55. data/spec/hearken/command/reload_spec.rb +0 -21
  56. data/spec/hearken/command/shuffle_spec.rb +0 -33
  57. data/spec/hearken/player_spec.rb +0 -38
  58. data/spec/hearken/range_expander_spec.rb +0 -28
  59. data/spec/spec_helper.rb +0 -16
data/bin/hearken_scrobble DELETED
@@ -1,35 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- $: << File.dirname(__FILE__)+'/../lib'
4
-
5
- command = ARGV.shift
6
-
7
- unless %w{setup start finish}.include? command
8
- puts 'usage:'
9
- puts ' hearken_scrobble setup'
10
- puts ' hearken_scrobble start PATH_TO_AUDIO_FILE'
11
- puts ' hearken_scrobble finish PATH_TO_AUDIO_FILE'
12
- exit 0
13
- end
14
-
15
- require 'hearken/indexing/ffmpeg_file'
16
- require 'hearken/scrobbler'
17
-
18
- scrobbler = Hearken::Scrobbler.new
19
- scrobbler.enabled = true
20
-
21
- if command == 'setup'
22
- scrobbler.setup
23
- exit 0
24
- end
25
-
26
- file = ARGV.shift
27
- track = Hearken::Indexing::FfmpegFile.from_file file
28
-
29
- if command == 'start'
30
- scrobbler.started track
31
- elsif command == 'finish'
32
- scrobbler.finished track
33
- else
34
- puts 'unknown command'
35
- end
data/bin/hearken_tags DELETED
@@ -1,11 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- $: << File.dirname(__FILE__)+'/../lib'
4
-
5
-
6
- require 'pp'
7
- require 'hearken/indexing/ffmpeg_file'
8
-
9
- ARGV.each do |path|
10
- pp Hearken::Indexing::FfmpegFile.from_file path
11
- end
@@ -1,32 +0,0 @@
1
- require 'hearken/command'
2
- require 'hearken/colour'
3
-
4
- class Hearken::Command::List
5
- include Hearken::Command
6
- include Hearken::Colour
7
-
8
- usage '*<word>'
9
- help <<EOF
10
- lists the contents of the track queue
11
- these results can optionally be filtered by specified words
12
- when playing, approximate times for each track will be displayed
13
- EOF
14
- execute do |text|
15
- @terms = text.split(/\W/)
16
- current = @player.current
17
- if current
18
- next_start_time = Time.at current.started
19
- show next_start_time, current
20
- end
21
- next_start_time += current.time.to_i if next_start_time && current.time
22
- @player.each do |track|
23
- show next_start_time, track
24
- next_start_time += track.time.to_i if next_start_time && track.time
25
- end
26
- end
27
-
28
- def show time, track
29
- return unless @terms.empty? or @terms.all? {|term| track.search_string.include? term }
30
- puts time ? "#{c time.strftime("%H:%M:%S %d/%m/%Y"), :blue} #{track}" : track
31
- end
32
- end
@@ -1,7 +0,0 @@
1
- require 'hearken/command'
2
-
3
- class Hearken::Command::Love
4
- include Hearken::Command
5
- help 'sends love for the current track to last fm'
6
- execute {|ignored| @player.love }
7
- end
@@ -1,7 +0,0 @@
1
- require 'hearken/command'
2
-
3
- class Hearken::Command::Profile
4
- include Hearken::Command
5
- help 'launches last fm profile'
6
- execute {|ignored| @player.profile }
7
- end
@@ -1,38 +0,0 @@
1
- require 'hearken/command'
2
- require 'hearken/colour'
3
-
4
- class Hearken::Command::Recent
5
- include Hearken::Command
6
- include Hearken::Colour
7
-
8
- usage '<count>'
9
- help 'lists the specified number of recently added albums'
10
- execute do |text|
11
- @player.library.reload unless @player.library.tracks
12
- all_tracks = @player.library.tracks.sort do |a, b|
13
- tc = a.timestamp <=> b.timestamp
14
- tc == 0 ? a.id <=> b.id : tc
15
- end
16
- maximum, current_album, tracks, total_count = text.to_i, nil, [], 0
17
- all_tracks.reverse.each do |track|
18
- unless current_album
19
- current_album = track.album
20
- tracks = [track]
21
- next
22
- end
23
- if current_album==track.album
24
- tracks << track
25
- else
26
- puts "#{c extract_artist(tracks), :yellow} #{c current_album, :cyan} #{tracks.size} tracks [#{tracks.last.search_id}-#{tracks.first.search_id}]"
27
- current_album = track.album
28
- tracks = [track]
29
- total_count += 1
30
- end
31
- break if total_count >= maximum
32
- end
33
- end
34
- private
35
- def extract_artist tracks
36
- tracks.map{|t| t.artist}.uniq.size == 1 ? tracks.first.artist : 'various artists'
37
- end
38
- end
@@ -1,15 +0,0 @@
1
- require 'hearken/command'
2
-
3
- class Hearken::Command::Remove
4
- include Hearken::Command
5
- usage '*<word>'
6
- help 'removes all tracks that match the specified criteria - specifying no criteria will flush entire queue'
7
- execute do |text|
8
- @terms = text.split(/\W/)
9
- ids = []
10
- while track = @player.dequeue
11
- ids << track.id unless @terms.all? {|term| track.search_string.include? term }
12
- end
13
- ids.each {|id| @player.enqueue id }
14
- end
15
- end
@@ -1,7 +0,0 @@
1
- require 'hearken/command'
2
-
3
- class Hearken::Command::Restart
4
- include Hearken::Command
5
- help 'stops and restarts the player (which will kill the current track)'
6
- execute {|ignored| @player.restart }
7
- end
@@ -1,14 +0,0 @@
1
- require 'hearken/command'
2
-
3
- class Hearken::Command::Scrobbling
4
- include Hearken::Command
5
- usage '<on|off>'
6
- help 'turns interaction with lastfm on or off'
7
- execute do |text|
8
- scrobbling = (text == 'on')
9
- return if @player.scrobbling == scrobbling
10
- puts scrobbling ? 'Turning scrobbling on' : 'Turning scrobbling off'
11
- @player.scrobbling = scrobbling
12
- @player.restart
13
- end
14
- end
@@ -1,7 +0,0 @@
1
- require 'hearken/command'
2
-
3
- class Hearken::Command::SetupScrobbling
4
- include Hearken::Command
5
- help 'runs through the steps required to get lastfm scrobbling working'
6
- execute {|ignored| @player.scrobbler.setup }
7
- end
@@ -1,13 +0,0 @@
1
- require 'hearken/command'
2
-
3
- class Hearken::Command::Shuffle
4
- include Hearken::Command
5
- help 'shuffles the current queue'
6
- execute do |ignored=nil|
7
- ids = []
8
- while track = @player.dequeue
9
- ids << track.id
10
- end
11
- ids.sort_by { rand }.each {|id| @player.enqueue id }
12
- end
13
- end
@@ -1,7 +0,0 @@
1
- require 'hearken/command'
2
-
3
- class Hearken::Command::Start
4
- include Hearken::Command
5
- help 'starts the player'
6
- execute {|ignored| @player.start }
7
- end
@@ -1,7 +0,0 @@
1
- require 'hearken/command'
2
-
3
- class Hearken::Command::Status
4
- include Hearken::Command
5
- help 'shows the current player status'
6
- execute {|ignored| puts @player.status }
7
- end
@@ -1,7 +0,0 @@
1
- require 'hearken/command'
2
-
3
- class Hearken::Command::Stop
4
- include Hearken::Command
5
- help 'stops the player'
6
- execute {|ignored| @player.stop }
7
- end
@@ -1,35 +0,0 @@
1
- module Hearken
2
- module Command
3
- attr_reader :usage, :help
4
-
5
- def self.included cls
6
- cls.extend ClassMethods
7
- end
8
-
9
- def self.load name, *args
10
- require "hearken/command/#{name}"
11
- classname = name.to_s.split('_').map{|s|s.capitalize}.join
12
- Hearken::Command.const_get(classname).new *args
13
- end
14
-
15
- def initialize player
16
- @player = player
17
- @usage = ''
18
- @help = ''
19
- end
20
-
21
- module ClassMethods
22
- def usage usage
23
- define_method(:usage) { usage }
24
- end
25
-
26
- def help help
27
- define_method(:help) { help }
28
- end
29
-
30
- def execute &block
31
- define_method :execute, block
32
- end
33
- end
34
- end
35
- end
@@ -1,15 +0,0 @@
1
- require 'hearken/monkey_violence'
2
-
3
- module Hearken::Notification
4
- end
5
-
6
- class Hearken::Notification::GrowlNotifier
7
- def initialize preferences
8
- @growlnotify = !`which growlnotify`.chomp.empty?
9
- @image_path = File.expand_path File.dirname(__FILE__)+'/../../../media/ice_cream.png'
10
- end
11
-
12
- def started track
13
- `growlnotify -t "Hearken unto ..." --image #{@image_path} -m \"#{track.to_short_s.escape_for_sh_quoted}\"` if @growlnotify
14
- end
15
- end
@@ -1,130 +0,0 @@
1
- require 'fileutils'
2
-
3
- require 'hearken/queue'
4
- require 'hearken/scrobbler'
5
- require 'hearken/notification/growl_notifier'
6
- require 'hearken/library'
7
- require 'hearken/colour'
8
-
9
- module Hearken
10
- class Player
11
- include Queue
12
- include Colour
13
- attr_reader :library, :scrobbler
14
-
15
- def initialize preferences
16
- @scrobbler = Scrobbler.new preferences
17
- @scrobbler.enabled = true
18
- @growl = Hearken::Notification::GrowlNotifier.new preferences
19
- @notifiers = [@scrobbler, @growl]
20
- @library = Library.new preferences
21
- @library.reload
22
- create_paths
23
- end
24
-
25
- def status
26
- if @pid
27
- track = self.current
28
- played = Time.now.to_i-track.started
29
- timing = "(#{c track.time.to_i-played, :yellow} remaining)" if track.time
30
- puts "#{c Time.at(track.started).strftime("%H:%M:%S %d/%m/%Y"), :blue}: #{track} #{timing}"
31
- else
32
- puts c 'not playing', :yellow
33
- end
34
- end
35
-
36
- def love
37
- @scrobbler.love current
38
- end
39
-
40
- def profile
41
- @scrobbler.profile
42
- end
43
-
44
- def current
45
- in_base_dir do
46
- (@pid and File.exist?('current_song')) ? YAML.load_file('current_song') : nil
47
- end
48
- end
49
-
50
- def register track
51
- track.started = Time.now.to_i
52
- in_base_dir do
53
- File.open('current_song', 'w') {|f| f.print track.to_yaml }
54
- File.open('history', 'a') {|f| f.puts "#{track.started},#{track.path}"}
55
- end
56
- end
57
-
58
- def notify_started track
59
- @notifiers.each {|notifier| notifier.started track if notifier.respond_to? :started}
60
- end
61
-
62
- def notify_finished track
63
- @notifiers.each {|notifier| notifier.finished track if notifier.respond_to? :finished}
64
- end
65
-
66
- def scrobbling
67
- @scrobbler.enabled
68
- end
69
-
70
- def scrobbling= tf
71
- @scrobbler.enabled = tf
72
- end
73
-
74
- def random_track
75
- @library.row (rand * @library.count).to_i
76
- end
77
-
78
- def start
79
- return if @pid
80
- if @library.count == 0
81
- puts 'Player can not be started with an empty library'
82
- puts 'Please run "hearken_index" in another shell and then \'reload\''
83
- return
84
- end
85
- @pid = fork do
86
- player_pid = nil
87
- Signal.trap('TERM') do
88
- Process.kill 'TERM', player_pid if player_pid
89
- exit
90
- end
91
- loop do
92
- track = dequeue || random_track
93
- next unless track
94
- unless File.exist? track.path
95
- puts "skipping track as #{track.path} does not exist"
96
- next
97
- end
98
- notify_started track
99
- register track
100
- player_pid = spawn play_command track.path
101
- Process.wait player_pid
102
- notify_finished track
103
- end
104
- end
105
- end
106
-
107
- def play_command path
108
- if %w{m4a mp3}.include? path.split('.').last
109
- "afplay \"#{path.escape("\`")}\""
110
- else
111
- "play -q \"#{path.escape("\`")}\""
112
- end.tap do |command|
113
- in_base_dir do
114
- File.open('player', 'a') {|f| f.puts command }
115
- end
116
- end
117
- end
118
-
119
- def stop
120
- return unless @pid
121
- Process.kill 'TERM', @pid
122
- @pid = nil
123
- end
124
-
125
- def restart
126
- stop
127
- start
128
- end
129
- end
130
- end
@@ -1,33 +0,0 @@
1
- require 'yaml'
2
- require 'hearken/paths'
3
- require 'hearken/monkey_violence'
4
-
5
- module Hearken; end
6
-
7
- module Hearken
8
- class Preferences
9
- include Hearken::Paths
10
-
11
- def initialize
12
- create_paths
13
- if File.exists? preferences_path
14
- @preferences = YAML.load_file preferences_path
15
- else
16
- @preferences = {}
17
- end
18
- end
19
-
20
- def [] key
21
- @preferences[key]
22
- end
23
-
24
- def []= key, value
25
- @preferences[key] = value
26
- persist
27
- end
28
-
29
- def persist
30
- File.open(preferences_path, 'w') {|f| f.puts @preferences.to_yaml}
31
- end
32
- end
33
- end
data/lib/hearken/queue.rb DELETED
@@ -1,33 +0,0 @@
1
- require 'hearken/paths'
2
-
3
- module Hearken::Queue
4
- include Hearken::Paths
5
-
6
- def enqueue id
7
- in_queue_dir do
8
- @sequence ||= 0
9
- @library.with_track id do |track|
10
- File.open("#{Time.now.to_i}-#{@sequence.to_s.rjust(8,'0')}.song", 'w') {|f| f.print track.to_yaml }
11
- @sequence += 1
12
- end
13
- end
14
- end
15
-
16
- def each
17
- in_queue_dir do
18
- Dir.glob('*.song').sort.each do |file|
19
- yield YAML.load_file file
20
- end
21
- end
22
- end
23
-
24
- def dequeue
25
- in_queue_dir do
26
- file = Dir.glob('*.song').sort.first
27
- return nil unless file
28
- hash = YAML.load_file file
29
- FileUtils.rm file
30
- hash
31
- end
32
- end
33
- end
@@ -1,82 +0,0 @@
1
- require 'hearken/preferences'
2
- require 'hearken/simple_scrobbler'
3
- require 'hearken/debug'
4
-
5
- # Modified version of simple_scrobbler gem from https://github.com/threedaymonk/simple_scrobbler
6
- module Hearken
7
- class Scrobbler
8
- API_KEY = '21f8c75ad38637220b20a03ad61219a4'
9
- SECRET = 'ab77019c84eef8bc16bcfd5ba8db0c5d'
10
- include Debug
11
-
12
- def initialize preferences=Hearken::Preferences.new
13
- @preferences = preferences
14
- end
15
-
16
- def enabled
17
- !!@scrobbler
18
- end
19
-
20
- def enabled= tf
21
- @scrobbler = nil
22
- if @preferences['lastfm'] and tf
23
- debug "Configuring scrobbler with #{@preferences['lastfm'].inspect}"
24
- user, session = *%w{user session_key}.map{|k| @preferences['lastfm'][k]}
25
- @scrobbler = SimpleScrobbler.new API_KEY, SECRET, user, session
26
- end
27
- end
28
-
29
- def finished track
30
- return unless @scrobbler
31
- debug "Scrobbling to last fm: #{track}"
32
- send_to_scrobbler :scrobble, track
33
- end
34
-
35
- def started track
36
- return unless @scrobbler
37
- debug "Updating now listening with last fm: #{track}"
38
- send_to_scrobbler :now_playing, track
39
- end
40
-
41
- def love track
42
- return unless @scrobbler and track
43
- debug "Sending love to last fm: #{track}"
44
- send_to_scrobbler :love, track
45
- end
46
-
47
- def profile
48
- return unless @scrobbler
49
- @scrobbler.with_profile_url {|url| system "open #{url}" }
50
- end
51
-
52
- def ask question
53
- print question
54
- $stdin.gets.chomp
55
- end
56
-
57
- def setup
58
- preferences = {}
59
- preferences['user'] = ask 'What is your lastfm user name ? '
60
- @scrobbler = SimpleScrobbler.new API_KEY, SECRET, preferences['user']
61
- preferences['session_key'] = @scrobbler.fetch_session_key do |url|
62
- system "open '#{url}'"
63
- ask 'Please hit enter when you\'ve allowed this application access to your account'
64
- end
65
- @preferences['lastfm'] = preferences
66
- end
67
- private
68
- def send_to_scrobbler message, track
69
- begin
70
- debug %w{artist title time album track}.map {|k| "#{k}=#{track.send(k)}"}.join(',')
71
- @scrobbler.send message, track.artist,
72
- track.title,
73
- :duration => track.time,
74
- :album => track.album,
75
- :trackNumber => track.track.to_i,
76
- :timestamp => Time.now.to_i
77
- rescue Exception => e
78
- puts "Failed to scrobble: #{e}"
79
- end
80
- end
81
- end
82
- end
@@ -1,94 +0,0 @@
1
- require "net/http"
2
- require "digest/md5"
3
- require "uri"
4
- require "cgi"
5
- require 'nokogiri'
6
- require 'hearken/debug'
7
-
8
- module Hearken; end
9
-
10
- class Hearken::SimpleScrobbler
11
- include Hearken::Debug
12
-
13
- SCROBBLER_URL = 'http://ws.audioscrobbler.com/2.0/'
14
-
15
- SubmissionError = Class.new(RuntimeError)
16
- SessionError = Class.new(RuntimeError)
17
-
18
- def initialize api_key, secret, user, session_key=nil
19
- @api_key = api_key
20
- @secret = secret
21
- @user = user
22
- @session_key = session_key
23
- end
24
-
25
- attr_reader :user, :api_key, :secret, :session_key
26
-
27
- def session_key
28
- @session_key or raise SessionError, "The session key must be set or fetched"
29
- end
30
-
31
- def fetch_session_key
32
- doc = lfm :get, 'auth.gettoken'
33
- request_token = doc.at('token').inner_text
34
- yield "http://www.last.fm/api/auth/?api_key=#{api_key}&token=#{request_token}"
35
- doc = lfm :get, 'auth.getsession', :token => request_token
36
- @session_key = doc.at('key').inner_text
37
- @user = doc.at('name').inner_text
38
- @session_key
39
- end
40
-
41
- def with_profile_url
42
- yield "http://www.last.fm/user/#{user}" if user
43
- end
44
-
45
- # http://www.last.fm/api/show?service=443
46
- def scrobble artist, title, params={}
47
- lfm_track 'track.scrobble', artist, title, params
48
- end
49
-
50
- # See http://www.last.fm/api/show?service=454 for more details
51
- def now_playing artist, title, params={}
52
- lfm_track 'track.updateNowPlaying', artist, title, params
53
- end
54
-
55
- # http://www.last.fm/api/show?service=260
56
- def love artist, title, params={}
57
- lfm_track 'track.love', artist, title, params
58
- end
59
- private
60
- def lfm_track method, artist, title, params
61
- doc = lfm :post, method, params.merge(:sk => session_key, :artist => artist, :track => title)
62
- status = doc.at('lfm')['status']
63
- raise SubmissionError, status unless status == 'ok'
64
- end
65
-
66
- def lfm get_or_post, method, parameters={}
67
- p = signed_parameters parameters.merge :api_key => api_key, :method => method
68
- debug p.inspect
69
- xml = self.send get_or_post, SCROBBLER_URL, p
70
- debug xml
71
- Nokogiri::XML xml
72
- end
73
-
74
- def get url, parameters
75
- query_string = sort_parameters(parameters).
76
- map{ |k, v| "#{k}=#{CGI.escape(v)}" }.
77
- join("&")
78
- Net::HTTP.get_response(URI.parse(url + "?" + query_string)).body
79
- end
80
-
81
- def post url, parameters
82
- Net::HTTP.post_form(URI.parse(url), parameters).body
83
- end
84
-
85
- def signed_parameters parameters
86
- sorted = sort_parameters parameters
87
- signature = Digest::MD5.hexdigest(sorted.flatten.join + secret)
88
- parameters.merge :api_sig => signature
89
- end
90
-
91
- def sort_parameters parameters
92
- parameters.map{ |k, v| [k.to_s, v.to_s] }.sort
93
- end
94
- end
data/media/ice_cream.png DELETED
Binary file
@@ -1,24 +0,0 @@
1
- require_relative '../../spec_helper'
2
- require 'hearken/command/enqueue'
3
-
4
- describe Hearken::Command::Enqueue do
5
- extend ShellShock::CommandSpec
6
-
7
- with_usage '*<id>'
8
- with_help 'enqueues the list of songs with the specified ids'
9
-
10
- before do
11
- @player = double 'player'
12
- @command = Hearken::Command::Enqueue.new @player
13
- end
14
-
15
- it 'should enqueue whatever is returned from the expander' do
16
- expander = double 'expander'
17
- allow(Hearken::RangeExpander).to receive(:new) { expander }
18
- allow(expander).to receive(:expand).with('some text') { [1,2,3] }
19
-
20
- [1,2,3].each {|id| expect(@player).to receive(:enqueue).with(id) }
21
-
22
- @command.execute "some text"
23
- end
24
- end