songbirdsh 0.0.2 → 0.0.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.
- 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
|