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 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