itunes-command 1.4-universal-darwin-9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/bin/itunes-command +3 -0
  2. data/itunes-command.rb +416 -0
  3. metadata +53 -0
@@ -0,0 +1,3 @@
1
+ #!/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby
2
+ require 'itunes-command'
3
+ ItunesCommand.run ARGV
data/itunes-command.rb ADDED
@@ -0,0 +1,416 @@
1
+ #!/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby
2
+
3
+ # If the shebang line doesn't point to your OS X installation of ruby, find it
4
+ # and correct the path above.
5
+ #
6
+ # itunes-command.rb
7
+ #
8
+ # Requirements:
9
+ #
10
+ # OS X Leopard
11
+ #
12
+ # Instructions:
13
+ #
14
+ # Save this file as 'itunes-command.rb' or whatever else you wish to call it.
15
+ #
16
+ # Run it with either:
17
+ #
18
+ # ruby itunes-command.rb
19
+ #
20
+ # or
21
+ #
22
+ # ./itunes-command.rb
23
+ #
24
+ # or itunes-command.rb
25
+ #
26
+ # The 2nd and 3rd options assume that you made the file executable with
27
+ #
28
+ # chmod u+x itunes-command.rb
29
+ #
30
+ # The 3d options also assumes that itunes-command.rb is on your PATH.
31
+ #
32
+ # Author: Daniel Choi
33
+ # Location: Cambridge, MA
34
+ # Affiliation: http://betahouse.org
35
+ # Email: dhchoi@gmail.com
36
+ # Project Homepage: http://danielchoi.com/software/itunes-command.html
37
+ #
38
+ # License: MIT
39
+ #
40
+ # Copyright (c) 2008 Daniel Choi
41
+ #
42
+ # Permission is hereby granted, free of charge, to any person
43
+ # obtaining a copy of this software and associated documentation
44
+ # files (the "Software"), to deal in the Software without
45
+ # restriction, including without limitation the rights to use,
46
+ # copy, modify, merge, publish, distribute, sublicense, and/or sell
47
+ # copies of the Software, and to permit persons to whom the
48
+ # Software is furnished to do so, subject to the following
49
+ # conditions:
50
+ #
51
+ # The above copyright notice and this permission notice shall be
52
+ # included in all copies or substantial portions of the Software.
53
+ #
54
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
55
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
56
+ # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
57
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
58
+ # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
59
+ # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
60
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
61
+ # OTHER DEALINGS IN THE SOFTWARE.
62
+
63
+ require 'rubygems'
64
+ $:.unshift File.dirname(__FILE__)
65
+ require 'readline'
66
+ require 'osx/cocoa'
67
+ OSX.require_framework 'ScriptingBridge'
68
+
69
+ class ITunes
70
+ QUEUE_PLAYLIST = 'itunes-command'
71
+ def initialize
72
+ @app = OSX::SBApplication.applicationWithBundleIdentifier("com.apple.iTunes")
73
+ end
74
+
75
+ # Delegate all other methods to the SBAppliation object for iTunes
76
+ def method_missing(message, *args)
77
+ @app.send(message, *args)
78
+ end
79
+
80
+ def party_shuffle
81
+ playlists.detect {|x| x.name == "Party Shuffle"}
82
+ end
83
+
84
+ def playlists
85
+ @app.sources.first.playlists
86
+ end
87
+
88
+ def playlist_by_name(name)
89
+ playlists.detect {|p| p.name == name}
90
+ end
91
+
92
+ def library
93
+ playlists.first
94
+ end
95
+
96
+ def artists(playlist=library)
97
+ return {} if playlist.tracks.empty?
98
+ artists = playlist.tracks.arrayByApplyingSelector("artist").select {|x| x.to_s =~ /\w/}
99
+ # count tracks per artist, which is represented to number of occurrences
100
+ artist = Hash.new(0)
101
+ artists.each do |name|
102
+ artist[name.to_s] += 1
103
+ end
104
+ artist
105
+ end
106
+
107
+ # Pass in a string to get matching tracks. Pass in an integer to search by
108
+ # databaseID
109
+ def find_track(query)
110
+ if query.is_a?(String)
111
+ library.searchFor_only(query, nil)
112
+ elsif query.is_a?(Integer) # lookup by databaseID
113
+ predicate = OSX::NSPredicate.predicateWithFormat("databaseID == #{query}")
114
+ # assume that only one track matches, and return it
115
+ library.tracks.filteredArrayUsingPredicate(predicate).first
116
+ end
117
+ end
118
+
119
+ def find_tracks(track_ids)
120
+ predicate = OSX::NSPredicate.predicateWithFormat("databaseID IN {%s}" % track_ids.join(','))
121
+ library.tracks.filteredArrayUsingPredicate(predicate)
122
+ end
123
+
124
+ def add_track_to_playlist(track, playlist)
125
+ if playlist.is_a?(String)
126
+ playlist = playlist_by_name(playlist)
127
+ end
128
+ track = track.duplicateTo(playlist)
129
+ # The following does not work:
130
+ # arg1 = OSX::NSArray.arrayWithArray([track])
131
+ # puts arg1.class
132
+ # puts playlist.class
133
+ # puts @app.add_to_(arg1, playlist)
134
+ end
135
+
136
+ def add_tracks_to_playlist(tracks, playlist, credit=nil)
137
+ if playlist.is_a?(String)
138
+ playlist = playlist_by_name(playlist)
139
+ end
140
+ if tracks.is_a?(Array) # need to convert Ruby array into NSArray
141
+ tracks = OSX::NSArray.arrayWithArray(tracks)
142
+ end
143
+ tracks.makeObjectsPerformSelector_withObject("setEnabled:", 1)
144
+ if credit
145
+ tracks.makeObjectsPerformSelector_withObject("setComment:", credit)
146
+ end
147
+ # Note the colon in the selector string
148
+ tracks.makeObjectsPerformSelector_withObject("duplicateTo:", playlist)
149
+ # enable all tracks - does not work
150
+ end
151
+
152
+ def remove_track_from_playlist(track, playlist)
153
+ # looks dangerous!
154
+ return
155
+ track.delete
156
+ end
157
+
158
+ def create_playlist(name)
159
+ return if playlist_by_name(name)
160
+ props = {:name => name}
161
+ playlist = @app.classForScriptingClass("playlist").alloc.initWithProperties(props)
162
+ playlists.insertObject_atIndex(playlist, 0)
163
+ playlist
164
+ end
165
+
166
+ # makes sure the queue playlist is selected. importance for pause/play, skip,
167
+ # etc.
168
+ def select_queue_playlist
169
+ browserWindows.first.view = queue
170
+ end
171
+
172
+ # This is the playlist that itunes-rails uses
173
+ def queue
174
+ playlist_by_name(QUEUE_PLAYLIST) || create_playlist(QUEUE_PLAYLIST)
175
+ end
176
+
177
+ def queue_track(track)
178
+ add_track_to_playlist(track, queue)
179
+ end
180
+
181
+ def queue_tracks(tracks,credit=nil)
182
+ add_tracks_to_playlist(tracks, queue, credit)
183
+ end
184
+
185
+ def clear_queue
186
+ queue.tracks.removeAllObjects
187
+ end
188
+
189
+ end
190
+ class ItunesCommand
191
+ VERSION = '1.3'
192
+ attr_accessor :playlist_mode
193
+ def initialize
194
+ @i = ITunes.new
195
+ @playlists = []
196
+ @tracks = []
197
+ @playlist_mode = false
198
+ end
199
+
200
+ def parse(method, *args)
201
+ self.send method, args
202
+ end
203
+
204
+ def search(string=nil)
205
+ unless string
206
+ puts "Please enter a search string"
207
+ return
208
+ end
209
+ @tracks = @i.find_track(string)
210
+ print_names(@tracks)
211
+ end
212
+
213
+ def search_artist(string=nil)
214
+ unless string
215
+ puts "Please enter a search string"
216
+ return
217
+ end
218
+ @tracks = @i.find_track(string)
219
+ print_names(@tracks)
220
+ end
221
+
222
+ def print_names(items)
223
+ items = Array(items)
224
+ rows = []
225
+ items.each_with_index do |t, i|
226
+ begin
227
+ puts("%2d %s : %s" % [i, t.artist, t.name])
228
+ rescue
229
+ end
230
+ end
231
+ items
232
+ end
233
+
234
+ def play(index)
235
+ if @tracks.empty?
236
+ puts "No tracks in buffer yet. Try searching."
237
+ return
238
+ end
239
+ track = @tracks[index.to_i]
240
+ puts "Playing '#{track.name}' by #{track.artist} from #{track.album}"
241
+ track.playOnce(1)
242
+ end
243
+
244
+ def stop
245
+ @i.stop
246
+ end
247
+
248
+ def volume(level=nil)
249
+ unless level
250
+ puts "The volume is #{@i.soundVolume} out of 100"
251
+ return
252
+ end
253
+ @i.soundVolume = level
254
+ puts "The volume set to #{@i.soundVolume} out of 100"
255
+ end
256
+
257
+ def +(steps=10)
258
+ @i.soundVolume = @i.soundVolume + steps.to_i
259
+ volume
260
+ end
261
+
262
+ def -(steps=10)
263
+ @i.soundVolume = @i.soundVolume - steps.to_i
264
+ volume
265
+ end
266
+
267
+ def playlists
268
+ @playlist_mode = true
269
+ puts "Showing playlists"
270
+ playlists = @i.playlists
271
+ playlists.each_with_index do |p,i|
272
+ puts("%2d %s" % [i, p.name])
273
+ end
274
+ end
275
+
276
+ def select_playlist(index)
277
+ @current_playlist = @i.playlists[index.to_i]
278
+ # show tracks
279
+ @tracks = @current_playlist.tracks
280
+ print_names(@tracks)
281
+ @playlist_mode = false
282
+ end
283
+
284
+ def playlist(index)
285
+ @playlists ||= playlists
286
+ puts @playlists[index].name
287
+ end
288
+
289
+ def queue(index)
290
+ @i.queue_track(track=@tracks[index.to_i])
291
+ puts "Added #{track.name} to the queue"
292
+ end
293
+
294
+ def show_queue
295
+ if @i.queue.tracks.empty?
296
+ puts "The queue is empty"
297
+ return
298
+ end
299
+ state = `osascript -e 'tell application "iTunes" to player state as string'`.strip
300
+ if state != 'stopped'
301
+ current_track_index = `osascript -e 'tell application "iTunes" to index of current track as string'`.to_i
302
+ else
303
+ current_track_index = nil
304
+ end
305
+ @i.queue.tracks.each_with_index do |t, i|
306
+ if current_track_index && current_track_index - 1 == i
307
+ puts "%s : %s <--- currently playing" % [t.artist, t.name]
308
+ else
309
+ puts "%s : %s" % [t.artist, t.name]
310
+ end
311
+ end
312
+ end
313
+
314
+ def clear_queue
315
+ @i.clear_queue
316
+ puts "Cleared queue"
317
+ end
318
+
319
+ def start_queue
320
+ @i.stop
321
+ @i.queue.playOnce(1)
322
+ track = @i.currentTrack
323
+ puts "Playing '#{track.name}' by #{track.artist} from #{track.album}"
324
+ end
325
+
326
+ def skip
327
+ @i.nextTrack
328
+ track = @i.currentTrack
329
+ puts "Playing '#{track.name}' by #{track.artist} from #{track.album}"
330
+ end
331
+
332
+ def artists
333
+ rows = []
334
+ (@artists=@i.artists).keys.sort_by {|x| x.downcase}.each_with_index do |k, i|
335
+ rows << ("%s (%s tracks)" % [k, @artists[k]])
336
+ end
337
+ pager(rows)
338
+ end
339
+
340
+ def pager(rows)
341
+ out = rows.join("\n")
342
+ IO.popen("less", "w") do |less|
343
+ less.puts out
344
+ less.close_write
345
+ end
346
+ end
347
+
348
+ HELP = <<END
349
+ itunes-command
350
+
351
+ Commands:
352
+
353
+ q quit
354
+ h show commands
355
+ s <string> searches for tracks matching <string>
356
+ <track number> plays a track (assumes you've done a search and got results)
357
+ a lists all artists in the library
358
+ v show the current volume level
359
+ v <level> sets the volume level (1-100)
360
+ + <increment> increases the volume by <increment>; default is 10 steps
361
+ - <increment> decreases the volume by <increment>; default is 10 steps
362
+ x stop
363
+ p shows all playlists
364
+ <playlist number> shows all the tracks in a playlist
365
+ l list all tracks in the queue (which will play tracks in succession)
366
+ n <track number> put a track in the queue
367
+ c clear the queue
368
+ g start playing tracks in the queue
369
+ k skip to next track in queue
370
+ END
371
+
372
+ COMMANDS = { 's' => :search,
373
+ 'x' => :stop , 'play' => :play, 'v' => :volume, 'a' => :artists, '+' => :+, '-' => '-',
374
+ 'p' => :playlists, 'select_playlist' => :select_playlist, 'l' => :show_queue, 'n' => :queue,
375
+ 'c' => :clear_queue, 'g' => 'start_queue', 'k' => 'skip'}
376
+
377
+ def self.run(argv=ARGV)
378
+ i = ItunesCommand.new
379
+ puts HELP
380
+ loop do
381
+ command = Readline.readline(">> ").chomp
382
+ if command =~ /^q/
383
+ exit
384
+ elsif command =~ /^h/
385
+ puts HELP
386
+ next
387
+ end
388
+ args = command.split(' ')
389
+ if args.first =~ /\d+/
390
+ if i.playlist_mode
391
+ args.unshift 'select_playlist'
392
+ else
393
+ args.unshift 'play'
394
+ end
395
+ end
396
+ method = COMMANDS[args.shift]
397
+ if method.nil?
398
+ puts "Sorry, I don't recognize that command."
399
+ next
400
+ end
401
+ begin
402
+ args.empty? ? i.send(method) : i.send(method, args.join(' '))
403
+ rescue ArgumentError
404
+ puts "Invalid command."
405
+ end
406
+ end
407
+
408
+ end
409
+
410
+ end
411
+
412
+
413
+ if __FILE__ == $0
414
+ ItunesCommand.run ARGV
415
+ end
416
+
metadata ADDED
@@ -0,0 +1,53 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: itunes-command
3
+ version: !ruby/object:Gem::Version
4
+ version: "1.4"
5
+ platform: universal-darwin-9
6
+ authors:
7
+ - Daniel Choi
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-08-17 00:00:00 -04:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Search, queue, and play iTunes tracks from the command line.
17
+ email: dhchoi@gmail.com
18
+ executables:
19
+ - itunes-command
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - itunes-command.rb
26
+ has_rdoc: false
27
+ homepage: http://cesareborgia.com/software/itunes-command/
28
+ post_install_message:
29
+ rdoc_options: []
30
+
31
+ require_paths:
32
+ - lib
33
+ required_ruby_version: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - ">="
36
+ - !ruby/object:Gem::Version
37
+ version: "0"
38
+ version:
39
+ required_rubygems_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ requirements: []
46
+
47
+ rubyforge_project:
48
+ rubygems_version: 1.0.1
49
+ signing_key:
50
+ specification_version: 2
51
+ summary: Search and play iTunes tracks from the command line
52
+ test_files: []
53
+