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 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
- * ? <command> - show commands/help for command
26
- * help <command> - show commands/help for command
27
- * + <id> - enqueue track with id
28
- * next - skip currently playing track
29
- * start - start music player
30
- * stop - stop music player
31
- * reload - reload track information (required for search)
32
- * search <text> - search in memory list of tracks (see reload)
33
- * exit - exit
34
- * quit - exit
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
- * Add documentation for all commands
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
@@ -0,0 +1,10 @@
1
+ begin
2
+ require 'gemesis/rake'
3
+ rescue Exception
4
+ puts "gemesis related tasks will only be available if you 'gem install gemesis'"
5
+ end
6
+
7
+ desc 'execute specifications'
8
+ task :test do
9
+ sh 'rspec spec'
10
+ end
@@ -1,32 +1,34 @@
1
1
  require 'shell_shock/context'
2
2
 
3
- require 'songbirdsh'
4
3
  require 'songbirdsh/player'
5
- require 'songbirdsh/library'
6
- require 'songbirdsh/command/enqueue'
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
- class Songbirdsh::Cli
15
- include ShellShock::Context
7
+ require 'yaml'
16
8
 
17
- def initialize
18
- library = Songbirdsh::Library.new
19
- player = Songbirdsh::Player.new library
20
- at_exit { player.stop }
21
- @prompt = "songbirdsh > "
22
- @commands = {
23
- 'show' => Songbirdsh::Command::ShowProperties.new(library),
24
- 'next' => Songbirdsh::Command::Restart.new(player),
25
- 'reload' => Songbirdsh::Command::Reload.new(library),
26
- 'search' => Songbirdsh::Command::Search.new(library),
27
- '+' => Songbirdsh::Command::Enqueue.new(player),
28
- 'start' => Songbirdsh::Command::Start.new(player),
29
- 'stop' => Songbirdsh::Command::Stop.new(player)
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 initialize player
5
- @player = 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 execute text
9
- text.split(/\W/).each {|id| @player.enqueue id }
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
- class Songbirdsh::Command::Reload
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
@@ -1,8 +1,6 @@
1
- class Songbirdsh::Command::Restart
2
- def initialize player
3
- @player = player
4
- end
1
+ require 'songbirdsh/command'
5
2
 
3
+ class Songbirdsh::Command::Restart < Songbirdsh::Command
6
4
  def execute ignored=nil
7
5
  @player.restart
8
6
  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
- class Songbirdsh::Command::Search
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
- @library.tracks.each do |track|
11
- if terms.all? {|term| track[:search_string].include? term }
12
- puts track[:display]
13
- matches << track[:id]
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
@@ -0,0 +1,11 @@
1
+ require 'songbirdsh/command'
2
+
3
+ class Songbirdsh::Command::SetupScrobbling < Songbirdsh::Command
4
+ def execute text
5
+ @player.scrobbler.setup
6
+ end
7
+
8
+ def help
9
+ 'runs through the steps required to get lastfm scrobbling working'
10
+ end
11
+ 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
@@ -1,8 +1,6 @@
1
- class Songbirdsh::Command::Start
2
- def initialize player
3
- @player = player
4
- end
1
+ require 'songbirdsh/command'
5
2
 
3
+ class Songbirdsh::Command::Start < Songbirdsh::Command
6
4
  def execute ignored=nil
7
5
  @player.start
8
6
  end
@@ -0,0 +1,12 @@
1
+ require 'yaml'
2
+ require 'songbirdsh/command'
3
+
4
+ class Songbirdsh::Command::Status < Songbirdsh::Command
5
+ def execute text
6
+ puts @player.status
7
+ end
8
+
9
+ def help
10
+ 'shows the current playing status'
11
+ end
12
+ end
@@ -1,8 +1,6 @@
1
- class Songbirdsh::Command::Stop
2
- def initialize player
3
- @player = player
4
- end
1
+ require 'songbirdsh/command'
5
2
 
3
+ class Songbirdsh::Command::Stop < Songbirdsh::Command
6
4
  def execute ignored=nil
7
5
  @player.stop
8
6
  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
@@ -0,0 +1,16 @@
1
+ module Songbirdsh
2
+ module Debug
3
+ def debug message
4
+ if ENV['DEBUG']
5
+ puts message
6
+ end
7
+ end
8
+
9
+ def pause
10
+ if ENV['DEBUG']
11
+ puts "Hit enter to continue"
12
+ gets
13
+ end
14
+ end
15
+ end
16
+ end
@@ -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 = `ls \"#{profiles}\"`.chomp
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
- yield to_track(row) if row[:property_id] == 3
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
- db[:resource_properties].each do |row|
39
- @tracks << to_track(row) if row[:property_id] == 3
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 "reloaded db with #{@tracks.size} tracks in #{Time.now.to_i-s} seconds"
70
+ puts "Reloaded db with #{@tracks.size} tracks in #{Time.now.to_i-s} seconds"
43
71
  end
44
72
  private
45
- def debug message
46
- if ENV['DEBUG']
47
- puts message
48
- gets
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 to_track row
53
- rest = row[:obj_secondary_sortable].split("\037")
54
- track = {
55
- :id => row[:media_item_id],
56
- :artist => row[:obj_sortable],
57
- :album => rest.shift,
58
- :disc => rest.shift.to_i,
59
- :number => rest.shift.to_i,
60
- :track => rest.shift
61
- }
62
- track[:search_string] = track[:artist]+track[:album]+track[:track]
63
- track[:display] = "#{track[:id]}: #{track[:artist]} #{track[:album]} #{track[:number]} #{track[:track]}"
64
- track
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
@@ -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
- class Songbirdsh::Player
8
- include Songbirdsh::Queue
9
+ require 'songbirdsh/scrobbler'
9
10
 
10
- def initialize library
11
- @library=library
12
- end
11
+ module Songbirdsh
12
+ class Player
13
+ include Queue
14
+ attr_reader :library, :scrobbler
15
+ attr_accessor :scrobbling, :matches
13
16
 
14
- def start
15
- if @pid
16
- puts "Already started (pid #{@pid})"
17
- return
17
+ def initialize preferences
18
+ @scrobbler = Scrobbler.new preferences
19
+ @scrobbling = true
20
+ @library = Library.new preferences
18
21
  end
19
- @pid = fork do
20
- player_pid = nil
21
- Signal.trap('TERM') do
22
- Process.kill 'TERM', player_pid if player_pid
23
- exit
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
- total_tracks = @library.with_db {|db| db[:media_items].count }
26
- loop do
27
- id = dequeue || (rand * total_tracks).to_i
28
- row = @library.with_db {|db| db[:media_items][:media_item_id=>id] }
29
- if row
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
- if path
32
- puts "playing #{id}: \"#{path}\""
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
- def stop
43
- return unless @pid
44
- Process.kill 'TERM', @pid
45
- @pid = nil
46
- end
79
+ def stop
80
+ return unless @pid
81
+ Process.kill 'TERM', @pid
82
+ @pid = nil
83
+ end
47
84
 
48
- def restart
49
- stop
50
- start
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
@@ -1,16 +1,26 @@
1
- module Songbirdsh::Queue
2
- def enqueue id
3
- @library.with_db do |db|
4
- File.open("#{Time.now.to_i}-#{id}.song", 'w') {|f| f.print db[:media_items][:media_item_id=>id].to_yaml }
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
- def dequeue
9
- file = Dir.glob('*.song').sort.first
10
- return nil unless file
11
- hash = YAML.load(File.read(file))
12
- id = hash[:media_item_id] if hash
13
- FileUtils.rm file
14
- id
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
@@ -1,7 +1,2 @@
1
1
  require 'rubygems'
2
- require 'sequel'
3
-
4
- module Songbirdsh
5
- module Command
6
- end
7
- end
2
+ require 'sequel'
@@ -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
@@ -0,0 +1,3 @@
1
+ $: << File.dirname(__FILE__)+'/../lib'
2
+
3
+ require 'shell_shock/command_spec'
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 0
8
- - 2
9
- version: 0.0.2
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-01-30 00:00:00 +10:00
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
- - 1
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
- - 0
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: rake
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
- - 8
100
- - 7
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: gemesis
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
- - 0
115
- - 4
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
- - gemspec
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