hearken 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
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