songbirdsh 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gemtest +0 -0
- data/HISTORY.rdoc +10 -0
- data/README.rdoc +17 -14
- data/Rakefile +10 -0
- data/lib/songbirdsh/cli.rb +27 -25
- data/lib/songbirdsh/command/enqueue.rb +10 -5
- data/lib/songbirdsh/command/list.rb +24 -0
- data/lib/songbirdsh/command/reload.rb +11 -5
- data/lib/songbirdsh/command/restart.rb +2 -4
- data/lib/songbirdsh/command/scrobbling.rb +19 -0
- data/lib/songbirdsh/command/search.rb +9 -9
- data/lib/songbirdsh/command/setup_scrobbling.rb +11 -0
- data/lib/songbirdsh/command/show_properties.rb +3 -6
- data/lib/songbirdsh/command/shuffle.rb +15 -0
- data/lib/songbirdsh/command/start.rb +2 -4
- data/lib/songbirdsh/command/status.rb +12 -0
- data/lib/songbirdsh/command/stop.rb +2 -4
- data/lib/songbirdsh/command.rb +13 -0
- data/lib/songbirdsh/debug.rb +16 -0
- data/lib/songbirdsh/library.rb +59 -25
- data/lib/songbirdsh/player.rb +69 -31
- data/lib/songbirdsh/preferences.rb +29 -0
- data/lib/songbirdsh/queue.rb +22 -12
- data/lib/songbirdsh/scrobbler.rb +74 -0
- data/lib/songbirdsh/track.rb +29 -0
- data/lib/songbirdsh.rb +1 -6
- data/spec/songbirdsh/command/enqueue_spec.rb +26 -0
- data/spec/songbirdsh/command/reload_spec.rb +20 -0
- data/spec/spec_helper.rb +3 -0
- metadata +39 -18
- data/gemspec +0 -22
data/.gemtest
ADDED
File without changes
|
data/HISTORY.rdoc
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
= 0.0.3
|
2
|
+
|
3
|
+
Introduced testing, lastfm support and a few other useful commands
|
4
|
+
|
5
|
+
* Started adding specifications
|
6
|
+
* Added rubygems-test and various other development dependencies
|
7
|
+
* added lastfm support - the setup_scrobbling and scrobble commands
|
8
|
+
* added randomisation of search results
|
9
|
+
* added a command to list the contents of the playing queue (with approximate times for when each track will play)
|
10
|
+
|
1
11
|
= 0.0.2
|
2
12
|
|
3
13
|
Enhanced search
|
data/README.rdoc
CHANGED
@@ -22,21 +22,24 @@ Here you sit expectantly in front of a computer at the command line.
|
|
22
22
|
|
23
23
|
If readline was correctly installed, you should find that tab completion works for all of the commands.
|
24
24
|
|
25
|
-
*
|
26
|
-
*
|
27
|
-
*
|
28
|
-
*
|
29
|
-
*
|
30
|
-
*
|
31
|
-
*
|
32
|
-
*
|
33
|
-
*
|
34
|
-
*
|
25
|
+
* ' - show current status (currently playing track)
|
26
|
+
* + <id> - enqueue track with id
|
27
|
+
* ? <command> - show commands/help for command
|
28
|
+
* exit - exit
|
29
|
+
* help <command> - show commands/help for command
|
30
|
+
* list <filter> - show current track queue
|
31
|
+
* next - skip currently playing track
|
32
|
+
* quit - exit
|
33
|
+
* reload - reload track information (required for search)
|
34
|
+
* scrobbling <on|off> - turn lastfm scrobbling on or off (note that this will skip the current track)
|
35
|
+
* search <filter> - search for tracks (places ids on the clipboard for convenient pasting to +)
|
36
|
+
* setup_scrobbling - runs through a setup wizard for lastfm integration
|
37
|
+
* show <id> - shows track details for the specified id
|
38
|
+
* shuffle - shuffles the list of tracks returned by the previous search
|
39
|
+
* start - start music player
|
40
|
+
* stop - stop music player
|
35
41
|
|
36
42
|
= Future plans for world domination
|
37
43
|
|
38
|
-
*
|
39
|
-
* Improve search
|
40
|
-
* Add music progress indicator
|
41
|
-
* Add some color
|
44
|
+
* See cardigan cards
|
42
45
|
* Get working on linux and windows
|
data/Rakefile
ADDED
data/lib/songbirdsh/cli.rb
CHANGED
@@ -1,32 +1,34 @@
|
|
1
1
|
require 'shell_shock/context'
|
2
2
|
|
3
|
-
require 'songbirdsh'
|
4
3
|
require 'songbirdsh/player'
|
5
|
-
require 'songbirdsh/
|
6
|
-
require 'songbirdsh/command
|
7
|
-
require 'songbirdsh/command/show_properties'
|
8
|
-
require 'songbirdsh/command/start'
|
9
|
-
require 'songbirdsh/command/reload'
|
10
|
-
require 'songbirdsh/command/stop'
|
11
|
-
require 'songbirdsh/command/restart'
|
12
|
-
require 'songbirdsh/command/search'
|
4
|
+
require 'songbirdsh/preferences'
|
5
|
+
require 'songbirdsh/command'
|
13
6
|
|
14
|
-
|
15
|
-
include ShellShock::Context
|
7
|
+
require 'yaml'
|
16
8
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
9
|
+
module Songbirdsh
|
10
|
+
class Cli
|
11
|
+
include ShellShock::Context
|
12
|
+
|
13
|
+
def with name, *aliases
|
14
|
+
aliases << name.to_s if aliases.empty?
|
15
|
+
add_command Command.load(name, @player), *aliases
|
16
|
+
end
|
17
|
+
|
18
|
+
def with_all *names
|
19
|
+
names.each {|name| with name}
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
preferences = Preferences.new
|
24
|
+
@player = Player.new preferences
|
25
|
+
at_exit { @player.stop }
|
26
|
+
@prompt = "songbirdsh > "
|
27
|
+
with :status, "'"
|
28
|
+
with :show_properties, 'show'
|
29
|
+
with :restart, 'next'
|
30
|
+
with :enqueue, '+'
|
31
|
+
with_all *%w{reload search start stop scrobbling shuffle list setup_scrobbling}
|
32
|
+
end
|
31
33
|
end
|
32
34
|
end
|
@@ -1,11 +1,16 @@
|
|
1
1
|
require 'yaml'
|
2
|
+
require 'songbirdsh/command'
|
2
3
|
|
3
|
-
class Songbirdsh::Command::Enqueue
|
4
|
-
def
|
5
|
-
@player
|
4
|
+
class Songbirdsh::Command::Enqueue < Songbirdsh::Command
|
5
|
+
def execute text
|
6
|
+
text.split(/[^0-9a-z]/).select {|s| s and !s.empty?}.each {|id| @player.enqueue id.to_i(36) }
|
6
7
|
end
|
7
8
|
|
8
|
-
def
|
9
|
-
|
9
|
+
def usage
|
10
|
+
'*<id>'
|
11
|
+
end
|
12
|
+
|
13
|
+
def help
|
14
|
+
'enqueues the list of songs with the specified ids'
|
10
15
|
end
|
11
16
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'songbirdsh/command'
|
2
|
+
|
3
|
+
class Songbirdsh::Command::List < Songbirdsh::Command
|
4
|
+
def execute text
|
5
|
+
@terms = text.split(/\W/)
|
6
|
+
current = @player.current
|
7
|
+
next_start_time = Time.at current.started
|
8
|
+
show next_start_time, current
|
9
|
+
next_start_time += current.duration
|
10
|
+
@player.each do |track|
|
11
|
+
show next_start_time, track
|
12
|
+
next_start_time += track.duration
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def show time, track
|
17
|
+
return unless @terms.empty? or @terms.all? {|term| track.search_string.include? term }
|
18
|
+
puts "#{time}\n\t#{track}"
|
19
|
+
end
|
20
|
+
|
21
|
+
def help
|
22
|
+
'lists the contents of the track queue (and approximate times for when each track will be played)'
|
23
|
+
end
|
24
|
+
end
|
@@ -1,9 +1,15 @@
|
|
1
|
-
|
2
|
-
def initialize library
|
3
|
-
@library = library
|
4
|
-
end
|
1
|
+
require 'songbirdsh/command'
|
5
2
|
|
3
|
+
class Songbirdsh::Command::Reload < Songbirdsh::Command
|
6
4
|
def execute ignored=nil
|
7
|
-
@library.reload
|
5
|
+
@player.library.reload
|
6
|
+
end
|
7
|
+
|
8
|
+
def usage
|
9
|
+
''
|
10
|
+
end
|
11
|
+
|
12
|
+
def help
|
13
|
+
'reloads the contents of the music library for fast searching'
|
8
14
|
end
|
9
15
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'songbirdsh/command'
|
2
|
+
|
3
|
+
class Songbirdsh::Command::Scrobbling < Songbirdsh::Command
|
4
|
+
def execute text
|
5
|
+
scrobbling = (text == 'on')
|
6
|
+
return if @player.scrobbling == scrobbling
|
7
|
+
puts scrobbling ? 'Turning scrobbling on' : 'Turning scrobbling off'
|
8
|
+
@player.scrobbling = scrobbling
|
9
|
+
@player.restart
|
10
|
+
end
|
11
|
+
|
12
|
+
def usage
|
13
|
+
'<on|off>'
|
14
|
+
end
|
15
|
+
|
16
|
+
def help
|
17
|
+
'turns interaction with lastfm on or off'
|
18
|
+
end
|
19
|
+
end
|
@@ -1,19 +1,19 @@
|
|
1
|
-
|
2
|
-
def initialize library
|
3
|
-
@library = library
|
4
|
-
end
|
1
|
+
require 'songbirdsh/command'
|
5
2
|
|
3
|
+
class Songbirdsh::Command::Search < Songbirdsh::Command
|
6
4
|
def execute text
|
7
5
|
terms = text.split(/\W/)
|
8
6
|
matches = []
|
9
|
-
@library.reload unless @library.tracks
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
7
|
+
@player.library.reload unless @player.library.tracks
|
8
|
+
matches = []
|
9
|
+
@player.library.tracks.each do |track|
|
10
|
+
if terms.all? {|term| track.search_string.include? term }
|
11
|
+
puts track
|
12
|
+
matches << track.search_id
|
14
13
|
end
|
15
14
|
end
|
16
15
|
puts "Found #{matches.size} matches (ids have been placed on clipboard)"
|
17
16
|
matches.join(' ').to_clipboard
|
17
|
+
@player.matches = matches
|
18
18
|
end
|
19
19
|
end
|
@@ -1,11 +1,8 @@
|
|
1
|
+
require 'songbirdsh/command'
|
1
2
|
require 'pp'
|
2
3
|
|
3
|
-
class Songbirdsh::Command::ShowProperties
|
4
|
-
def initialize library
|
5
|
-
@library = library
|
6
|
-
end
|
7
|
-
|
4
|
+
class Songbirdsh::Command::ShowProperties < Songbirdsh::Command
|
8
5
|
def execute id
|
9
|
-
@library.with_track(id) {|track| pp track }
|
6
|
+
@player.library.with_track(id.to_i(36)) {|track| pp track }
|
10
7
|
end
|
11
8
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'songbirdsh/command'
|
2
|
+
|
3
|
+
class Songbirdsh::Command::Shuffle < Songbirdsh::Command
|
4
|
+
def execute text
|
5
|
+
if @player.matches
|
6
|
+
@player.matches.sort_by { rand }.join(' ').to_clipboard
|
7
|
+
else
|
8
|
+
puts 'nothing to shuffle - please search for some tracks'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def help
|
13
|
+
'shuffles the results from the last search and places them on the clipboard'
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Songbirdsh
|
2
|
+
class Command
|
3
|
+
def self.load name, *args
|
4
|
+
require "songbirdsh/command/#{name}"
|
5
|
+
classname = name.to_s.split('_').map{|s|s.capitalize}.join
|
6
|
+
Songbirdsh::Command.const_get(classname).new *args
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize player
|
10
|
+
@player = player
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/songbirdsh/library.rb
CHANGED
@@ -1,18 +1,36 @@
|
|
1
|
+
require 'sequel'
|
2
|
+
require 'songbirdsh/track'
|
3
|
+
require 'songbirdsh/debug'
|
4
|
+
|
1
5
|
class Songbirdsh::Library
|
6
|
+
include Songbirdsh::Debug
|
2
7
|
attr_reader :tracks
|
3
8
|
|
4
|
-
def initialize
|
9
|
+
def initialize preferences
|
5
10
|
home = File.expand_path '~'
|
6
11
|
debug "Home Directory is \"#{home}\""
|
7
12
|
songbird_home = "#{home}/Library/Application Support/Songbird2"
|
8
13
|
debug "Songbird home is \"#{songbird_home}\""
|
9
14
|
profiles = "#{songbird_home}/Profiles"
|
10
|
-
profile
|
11
|
-
debug "found profile \"#{profile}\""
|
12
|
-
@db_path = "#{profiles}/#{profile}/db/main@library.songbirdnest.com.db"
|
15
|
+
preferences['profile'] ||= choose_profile profiles
|
16
|
+
debug "found profile \"#{preferences['profile']}\""
|
17
|
+
@db_path = "#{profiles}/#{preferences['profile']}/db/main@library.songbirdnest.com.db"
|
13
18
|
debug "using db \"#{@db_path}\""
|
14
19
|
end
|
15
20
|
|
21
|
+
def choose_profile path
|
22
|
+
choices = Dir.entries(path).select {|path| !['.','..'].include?(path) }
|
23
|
+
raise 'no profiles found' if choices.empty?
|
24
|
+
return choices.first if choices.size == 1
|
25
|
+
loop do
|
26
|
+
choices.each {|choice| puts "* #{choice}"}
|
27
|
+
print "please enter your preferred songbird profile > "
|
28
|
+
subset = choices.grep(/#{gets.chomp}/)
|
29
|
+
return subset.first if subset.size == 1
|
30
|
+
puts 'invalid choice - please enter the full name of the profile'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
16
34
|
def with_db
|
17
35
|
db = Sequel.sqlite @db_path
|
18
36
|
begin
|
@@ -24,43 +42,59 @@ class Songbirdsh::Library
|
|
24
42
|
end
|
25
43
|
|
26
44
|
def with_track id
|
45
|
+
track = Songbirdsh::Track.new id
|
27
46
|
with_db do |db|
|
28
47
|
db[:resource_properties].filter(:media_item_id=>id).each do |row|
|
29
|
-
|
48
|
+
append_to_track track, row
|
30
49
|
end
|
31
50
|
end
|
51
|
+
yield track
|
32
52
|
end
|
33
53
|
|
34
54
|
def reload
|
35
55
|
s = Time.now.to_i
|
36
56
|
@tracks = []
|
37
57
|
with_db do |db|
|
38
|
-
|
39
|
-
|
58
|
+
current_media_item_id, current_track = nil, nil
|
59
|
+
db[:resource_properties].order(:media_item_id).each do |row|
|
60
|
+
unless row[:media_item_id] == current_media_item_id
|
61
|
+
append current_track
|
62
|
+
current_track = Songbirdsh::Track.new row[:media_item_id]
|
63
|
+
debug "Created new track with id #{current_track.id}"
|
64
|
+
current_media_item_id = current_track.id
|
65
|
+
end
|
66
|
+
append_to_track current_track, row
|
40
67
|
end
|
68
|
+
append current_track
|
41
69
|
end
|
42
|
-
puts "
|
70
|
+
puts "Reloaded db with #{@tracks.size} tracks in #{Time.now.to_i-s} seconds"
|
43
71
|
end
|
44
72
|
private
|
45
|
-
def
|
46
|
-
|
47
|
-
|
48
|
-
|
73
|
+
def append track
|
74
|
+
unless track and track.valid?
|
75
|
+
debug "Discarding #{track.inspect}"
|
76
|
+
return
|
49
77
|
end
|
78
|
+
@tracks << track
|
79
|
+
debug "Appended #{track}"
|
80
|
+
debug "Now up to #{@tracks.size} tracks"
|
81
|
+
pause
|
50
82
|
end
|
51
83
|
|
52
|
-
def
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
84
|
+
def append_to_track track, row
|
85
|
+
debug row.inspect
|
86
|
+
case row[:property_id]
|
87
|
+
when 1; track.track = row[:obj_searchable]
|
88
|
+
when 2; track.album = row[:obj_searchable]
|
89
|
+
when 3; track.artist = row[:obj_searchable]
|
90
|
+
when 4; track.duration = row[:obj].to_i/1000000
|
91
|
+
when 5; track.genre = row[:obj_searchable]
|
92
|
+
when 6; track.number = row[:obj].to_i
|
93
|
+
when 7; track.year = row[:obj].to_i
|
94
|
+
when 8; track.disc = row[:obj].to_i
|
95
|
+
when 9; track.disc_total = row[:obj].to_i
|
96
|
+
when 10; track.track_total = row[:obj].to_i
|
97
|
+
when 23; track.label = row[:obj_searchable]
|
98
|
+
end
|
65
99
|
end
|
66
100
|
end
|
data/lib/songbirdsh/player.rb
CHANGED
@@ -1,52 +1,90 @@
|
|
1
1
|
require 'songbirdsh/queue'
|
2
|
+
require 'songbirdsh/library'
|
3
|
+
|
2
4
|
require 'cgi'
|
3
5
|
require 'yaml'
|
4
6
|
require 'fileutils'
|
5
7
|
require 'splat'
|
6
8
|
|
7
|
-
|
8
|
-
include Songbirdsh::Queue
|
9
|
+
require 'songbirdsh/scrobbler'
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
11
|
+
module Songbirdsh
|
12
|
+
class Player
|
13
|
+
include Queue
|
14
|
+
attr_reader :library, :scrobbler
|
15
|
+
attr_accessor :scrobbling, :matches
|
13
16
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
17
|
+
def initialize preferences
|
18
|
+
@scrobbler = Scrobbler.new preferences
|
19
|
+
@scrobbling = true
|
20
|
+
@library = Library.new preferences
|
18
21
|
end
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
22
|
+
|
23
|
+
def status
|
24
|
+
if @pid
|
25
|
+
track = self.current
|
26
|
+
puts "Since #{Time.at(track.started)}\n\t#{track}"
|
27
|
+
played = Time.now.to_i-track.started
|
28
|
+
puts "#{played} seconds (#{track.duration-played} remaining)"
|
29
|
+
else
|
30
|
+
puts 'not playing'
|
24
31
|
end
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
32
|
+
end
|
33
|
+
|
34
|
+
def current
|
35
|
+
YAML.load(File.read('current_song'))
|
36
|
+
end
|
37
|
+
|
38
|
+
def register track
|
39
|
+
track.started = Time.now.to_i
|
40
|
+
File.open('current_song', 'w') {|f| f.print track.to_yaml }
|
41
|
+
end
|
42
|
+
|
43
|
+
def start
|
44
|
+
if @pid
|
45
|
+
puts "Already started (pid #{@pid})"
|
46
|
+
return
|
47
|
+
end
|
48
|
+
@pid = fork do
|
49
|
+
player_pid = nil
|
50
|
+
Signal.trap('TERM') do
|
51
|
+
Process.kill 'TERM', player_pid if player_pid
|
52
|
+
exit
|
53
|
+
end
|
54
|
+
total_tracks = @library.with_db {|db| db[:media_items].count }
|
55
|
+
loop do
|
56
|
+
id = dequeue || (rand * total_tracks).to_i
|
57
|
+
row = @library.with_db {|db| db[:media_items][:media_item_id=>id] }
|
58
|
+
unless row
|
59
|
+
puts "track with id #{id} did not exist"
|
60
|
+
next
|
61
|
+
end
|
30
62
|
path = CGI.unescape(row[:content_url].slice(7..-1)) if row[:content_url] =~ /^file/
|
31
|
-
|
32
|
-
puts "
|
63
|
+
unless path and File.exist? path
|
64
|
+
puts "track with id #{id} did not refer to a file"
|
65
|
+
next
|
66
|
+
end
|
67
|
+
@library.with_track(id) do |track|
|
68
|
+
@scrobbler.update track if @scrobbling
|
69
|
+
register track
|
33
70
|
player_pid = path.to_player
|
34
71
|
Process.wait player_pid
|
72
|
+
@scrobbler.scrobble track if @scrobbling
|
35
73
|
end
|
36
74
|
end
|
37
75
|
end
|
76
|
+
puts "Started (pid #{@pid})"
|
38
77
|
end
|
39
|
-
puts "Started (pid #{@pid})"
|
40
|
-
end
|
41
78
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
79
|
+
def stop
|
80
|
+
return unless @pid
|
81
|
+
Process.kill 'TERM', @pid
|
82
|
+
@pid = nil
|
83
|
+
end
|
47
84
|
|
48
|
-
|
49
|
-
|
50
|
-
|
85
|
+
def restart
|
86
|
+
stop
|
87
|
+
start
|
88
|
+
end
|
51
89
|
end
|
52
90
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Songbirdsh
|
2
|
+
class Preferences
|
3
|
+
def initialize
|
4
|
+
@preference_path = home_path '.songbirdsh'
|
5
|
+
if File.exists? @preference_path
|
6
|
+
@preferences = YAML.load File.read(@preference_path)
|
7
|
+
else
|
8
|
+
@preferences = {}
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def home_path *paths
|
13
|
+
File.join File.expand_path('~'), *paths
|
14
|
+
end
|
15
|
+
|
16
|
+
def [] key
|
17
|
+
@preferences[key]
|
18
|
+
end
|
19
|
+
|
20
|
+
def []= key, value
|
21
|
+
@preferences[key] = value
|
22
|
+
persist
|
23
|
+
end
|
24
|
+
|
25
|
+
def persist
|
26
|
+
File.open(@preference_path, 'w') {|f| f.puts @preferences.to_yaml}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/songbirdsh/queue.rb
CHANGED
@@ -1,16 +1,26 @@
|
|
1
|
-
module Songbirdsh
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
module Songbirdsh
|
2
|
+
module Queue
|
3
|
+
def enqueue id
|
4
|
+
@sequence ||= 0
|
5
|
+
@library.with_track id do |track|
|
6
|
+
File.open("#{Time.now.to_i}-#{@sequence.to_s.rjust(8,'0')}.song", 'w') {|f| f.print track.to_yaml }
|
7
|
+
@sequence += 1
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def each
|
12
|
+
Dir.glob('*.song').sort.each do |file|
|
13
|
+
yield YAML.load File.read(file)
|
14
|
+
end
|
5
15
|
end
|
6
|
-
end
|
7
16
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
17
|
+
def dequeue
|
18
|
+
file = Dir.glob('*.song').sort.first
|
19
|
+
return nil unless file
|
20
|
+
hash = YAML.load(File.read(file))
|
21
|
+
id = hash[:id] if hash
|
22
|
+
FileUtils.rm file
|
23
|
+
id
|
24
|
+
end
|
15
25
|
end
|
16
26
|
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'splat'
|
2
|
+
require 'simple_scrobbler'
|
3
|
+
require 'songbirdsh/debug'
|
4
|
+
|
5
|
+
module Songbirdsh
|
6
|
+
class Scrobbler
|
7
|
+
include Debug
|
8
|
+
|
9
|
+
def initialize preferences
|
10
|
+
@preferences = preferences
|
11
|
+
if preferences['lastfm']
|
12
|
+
@scrobbler = SimpleScrobbler.new *%w{api_key secret user session_key}.map{|k| [k]}
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def scrobble track
|
17
|
+
return unless @scrobbler
|
18
|
+
debug "Scrobbling to last fm: #{track.inspect}"
|
19
|
+
send_to_scrobbler :submit, track
|
20
|
+
end
|
21
|
+
|
22
|
+
def update track
|
23
|
+
return unless @scrobbler
|
24
|
+
debug "Updating now listening with last fm: #{track.inspect}"
|
25
|
+
send_to_scrobbler :now_playing, track
|
26
|
+
end
|
27
|
+
|
28
|
+
def ask question
|
29
|
+
print question
|
30
|
+
gets.chomp
|
31
|
+
end
|
32
|
+
|
33
|
+
def setup
|
34
|
+
puts <<-EOF
|
35
|
+
Because of the way lastfm authentication works, setting up lastfm involves two steps:
|
36
|
+
Firstly, you need to register that you are developing an application. This will give you an api key and an associated 'secret'
|
37
|
+
Secondly, you need to gives your application access your lastfm account. This will give a authenticated session.
|
38
|
+
|
39
|
+
Step 1. Logging in to your lastfm account and register an application
|
40
|
+
This will launch a browser. You need to log in and fill out the API registration form
|
41
|
+
EOF
|
42
|
+
answer = ask 'Are you ready ? '
|
43
|
+
return unless answer.downcase.start_with? 'y'
|
44
|
+
"http://www.last.fm/api/account".to_launcher
|
45
|
+
preferences = {}
|
46
|
+
preferences['api_key'] = ask 'What is your API key ? '
|
47
|
+
preferences['secret'] = ask 'What is your secret ? '
|
48
|
+
puts <<-EOF
|
49
|
+
Now you've got you application details, you need to create an authentication session between your application and your lastfm account
|
50
|
+
Step 2. Authorising your application to access your lastfm account
|
51
|
+
This will launch another browser. You just need to give your application permission to access your account
|
52
|
+
EOF
|
53
|
+
preferences['user'] = ask 'What is your lastfm user name ? '
|
54
|
+
@scrobbler = SimpleScrobbler.new preferences['api_key'], preferences['secret'], preferences['user']
|
55
|
+
preferences['session_key'] = @scrobbler.fetch_session_key do |url|
|
56
|
+
url.to_launcher
|
57
|
+
ask 'Hit enter when you\'ve allowed your application access to your account'
|
58
|
+
end
|
59
|
+
@preferences['lastfm'] = preferences
|
60
|
+
end
|
61
|
+
private
|
62
|
+
def send_to_scrobbler message, track
|
63
|
+
begin
|
64
|
+
@scrobbler.send message, track[:artist],
|
65
|
+
track[:track],
|
66
|
+
:length => track[:duration],
|
67
|
+
:album => track[:album],
|
68
|
+
:track_number => track[:number]
|
69
|
+
rescue Exception => e
|
70
|
+
puts "Failed to scrobble: #{e}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Songbirdsh
|
2
|
+
class Track
|
3
|
+
attr_accessor *%w{id track album artist duration genre number year disc disc_total track_total label started}
|
4
|
+
|
5
|
+
def initialize id
|
6
|
+
@id = id
|
7
|
+
end
|
8
|
+
|
9
|
+
def [] key
|
10
|
+
self.send key
|
11
|
+
end
|
12
|
+
|
13
|
+
def valid?
|
14
|
+
@track
|
15
|
+
end
|
16
|
+
|
17
|
+
def search_id
|
18
|
+
id.to_s 36
|
19
|
+
end
|
20
|
+
|
21
|
+
def search_string
|
22
|
+
"#{self.artist}#{self.album}#{self.track}"
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_s
|
26
|
+
"#{self.search_id}: #{self.artist} - #{self.album} - #{self.number} #{self.track} (#{self.duration})"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/songbirdsh.rb
CHANGED
@@ -0,0 +1,26 @@
|
|
1
|
+
require File.dirname(__FILE__)+'/../../spec_helper'
|
2
|
+
require 'songbirdsh/command/enqueue'
|
3
|
+
|
4
|
+
describe Songbirdsh::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 = stub('player')
|
12
|
+
@command = Songbirdsh::Command::Enqueue.new @player
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should enqueue a single track' do
|
16
|
+
@player.should_receive(:enqueue).with(1371)
|
17
|
+
@command.execute '123'
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should enqueue multiple tracks separated by any non digit' do
|
21
|
+
%w{1371 5370 9369}.each do |id|
|
22
|
+
@player.should_receive(:enqueue).with(id.to_i)
|
23
|
+
end
|
24
|
+
@command.execute "123 \t 456 , 789 "
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require File.dirname(__FILE__)+'/../../spec_helper'
|
2
|
+
require 'songbirdsh/command/reload'
|
3
|
+
|
4
|
+
describe Songbirdsh::Command::Reload do
|
5
|
+
extend ShellShock::CommandSpec
|
6
|
+
|
7
|
+
with_usage ''
|
8
|
+
with_help 'reloads the contents of the music library for fast searching'
|
9
|
+
|
10
|
+
before do
|
11
|
+
@library = stub('library')
|
12
|
+
@player = stub('player', :library => @library)
|
13
|
+
@command = Songbirdsh::Command::Reload.new @player
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should reload the library' do
|
17
|
+
@library.should_receive(:reload)
|
18
|
+
@command.execute '123'
|
19
|
+
end
|
20
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: 0.0.
|
8
|
+
- 3
|
9
|
+
version: 0.0.3
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Mark Ryall
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2011-
|
17
|
+
date: 2011-02-20 00:00:00 +10:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -27,8 +27,7 @@ dependencies:
|
|
27
27
|
- !ruby/object:Gem::Version
|
28
28
|
segments:
|
29
29
|
- 0
|
30
|
-
|
31
|
-
version: "0.1"
|
30
|
+
version: "0"
|
32
31
|
type: :runtime
|
33
32
|
version_requirements: *id001
|
34
33
|
- !ruby/object:Gem::Dependency
|
@@ -41,9 +40,7 @@ dependencies:
|
|
41
40
|
- !ruby/object:Gem::Version
|
42
41
|
segments:
|
43
42
|
- 0
|
44
|
-
|
45
|
-
- 5
|
46
|
-
version: 0.0.5
|
43
|
+
version: "0"
|
47
44
|
type: :runtime
|
48
45
|
version_requirements: *id002
|
49
46
|
- !ruby/object:Gem::Dependency
|
@@ -87,7 +84,7 @@ dependencies:
|
|
87
84
|
type: :runtime
|
88
85
|
version_requirements: *id005
|
89
86
|
- !ruby/object:Gem::Dependency
|
90
|
-
name:
|
87
|
+
name: simple_scrobbler
|
91
88
|
prerelease: false
|
92
89
|
requirement: &id006 !ruby/object:Gem::Requirement
|
93
90
|
none: false
|
@@ -96,13 +93,11 @@ dependencies:
|
|
96
93
|
- !ruby/object:Gem::Version
|
97
94
|
segments:
|
98
95
|
- 0
|
99
|
-
|
100
|
-
|
101
|
-
version: 0.8.7
|
102
|
-
type: :development
|
96
|
+
version: "0"
|
97
|
+
type: :runtime
|
103
98
|
version_requirements: *id006
|
104
99
|
- !ruby/object:Gem::Dependency
|
105
|
-
name:
|
100
|
+
name: rake
|
106
101
|
prerelease: false
|
107
102
|
requirement: &id007 !ruby/object:Gem::Requirement
|
108
103
|
none: false
|
@@ -111,11 +106,23 @@ dependencies:
|
|
111
106
|
- !ruby/object:Gem::Version
|
112
107
|
segments:
|
113
108
|
- 0
|
114
|
-
-
|
115
|
-
|
116
|
-
version: 0.0.4
|
109
|
+
- 8
|
110
|
+
version: "0.8"
|
117
111
|
type: :development
|
118
112
|
version_requirements: *id007
|
113
|
+
- !ruby/object:Gem::Dependency
|
114
|
+
name: rspec
|
115
|
+
prerelease: false
|
116
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
117
|
+
none: false
|
118
|
+
requirements:
|
119
|
+
- - ~>
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
segments:
|
122
|
+
- 2
|
123
|
+
version: "2"
|
124
|
+
type: :development
|
125
|
+
version_requirements: *id008
|
119
126
|
description: |
|
120
127
|
A command line jukebox music player that uses your songbird music player database
|
121
128
|
|
@@ -129,21 +136,35 @@ extra_rdoc_files: []
|
|
129
136
|
files:
|
130
137
|
- lib/songbirdsh/cli.rb
|
131
138
|
- lib/songbirdsh/command/enqueue.rb
|
139
|
+
- lib/songbirdsh/command/list.rb
|
132
140
|
- lib/songbirdsh/command/reload.rb
|
133
141
|
- lib/songbirdsh/command/restart.rb
|
142
|
+
- lib/songbirdsh/command/scrobbling.rb
|
134
143
|
- lib/songbirdsh/command/search.rb
|
144
|
+
- lib/songbirdsh/command/setup_scrobbling.rb
|
135
145
|
- lib/songbirdsh/command/show_properties.rb
|
146
|
+
- lib/songbirdsh/command/shuffle.rb
|
136
147
|
- lib/songbirdsh/command/start.rb
|
148
|
+
- lib/songbirdsh/command/status.rb
|
137
149
|
- lib/songbirdsh/command/stop.rb
|
150
|
+
- lib/songbirdsh/command.rb
|
151
|
+
- lib/songbirdsh/debug.rb
|
138
152
|
- lib/songbirdsh/library.rb
|
139
153
|
- lib/songbirdsh/player.rb
|
154
|
+
- lib/songbirdsh/preferences.rb
|
140
155
|
- lib/songbirdsh/queue.rb
|
156
|
+
- lib/songbirdsh/scrobbler.rb
|
157
|
+
- lib/songbirdsh/track.rb
|
141
158
|
- lib/songbirdsh.rb
|
159
|
+
- spec/songbirdsh/command/enqueue_spec.rb
|
160
|
+
- spec/songbirdsh/command/reload_spec.rb
|
161
|
+
- spec/spec_helper.rb
|
142
162
|
- bin/songbirdsh
|
143
163
|
- README.rdoc
|
144
164
|
- HISTORY.rdoc
|
145
165
|
- MIT-LICENSE
|
146
|
-
-
|
166
|
+
- .gemtest
|
167
|
+
- Rakefile
|
147
168
|
has_rdoc: true
|
148
169
|
homepage: http://github.com/markryall/songbirdsh
|
149
170
|
licenses: []
|
data/gemspec
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
Gem::Specification.new do |spec|
|
2
|
-
spec.name = 'songbirdsh'
|
3
|
-
spec.version = '0.0.2'
|
4
|
-
spec.summary = 'command line jukebox music player'
|
5
|
-
spec.description = <<-EOF
|
6
|
-
A command line jukebox music player that uses your songbird music player database
|
7
|
-
EOF
|
8
|
-
spec.authors << 'Mark Ryall'
|
9
|
-
spec.email = 'mark@ryall.name'
|
10
|
-
spec.homepage = 'http://github.com/markryall/songbirdsh'
|
11
|
-
spec.files = Dir['lib/**/*'] + Dir['bin/*'] + ['README.rdoc', 'HISTORY.rdoc','MIT-LICENSE', 'gemspec']
|
12
|
-
spec.executables << 'songbirdsh'
|
13
|
-
|
14
|
-
spec.add_dependency 'splat', '~>0.1'
|
15
|
-
spec.add_dependency 'shell_shock', '~>0.0.5'
|
16
|
-
spec.add_dependency 'sequel', '~> 3'
|
17
|
-
spec.add_dependency 'splat', '~> 0.1'
|
18
|
-
spec.add_dependency 'sqlite3', '~> 1'
|
19
|
-
|
20
|
-
spec.add_development_dependency 'rake', '~>0.8.7'
|
21
|
-
spec.add_development_dependency 'gemesis', '~>0.0.4'
|
22
|
-
end
|