danchoi-itunes-command 1.6.3-x86-darwin-9

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.
Files changed (3) hide show
  1. data/bin/itunes-command +4 -0
  2. data/lib/itunes_command.rb +445 -0
  3. metadata +55 -0
@@ -0,0 +1,4 @@
1
+ #!/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby
2
+ require 'rubygems'
3
+ require 'itunes_command'
4
+ ItunesCommand.run ARGV
@@ -0,0 +1,445 @@
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.6.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
+ if index =~ /\d+-\d+/
291
+ start = index.split('-').first.to_i
292
+ last = index.split('-').last.to_i
293
+ Array(start..last).each do |i|
294
+ @i.queue_track(track=@tracks[i])
295
+ puts "Added #{track.name} to the queue"
296
+ end
297
+ else
298
+ @i.queue_track(track=@tracks[index.to_i])
299
+ puts "Added #{track.name} to the queue"
300
+ end
301
+ end
302
+
303
+ def playpause
304
+ puts %x{tell application "iTunes"
305
+ playpause
306
+ end tell}
307
+ state = `osascript -e 'tell application "iTunes" to player state as string'`.strip
308
+ puts state
309
+ return
310
+ if state == 'stopped'
311
+ @i.currentTrack.playOnce(1)
312
+ else
313
+ puts @i.currentTrack.name
314
+ @i.pause
315
+ end
316
+ end
317
+
318
+ def show_queue
319
+ if @i.queue.tracks.empty?
320
+ puts "The queue is empty"
321
+ return
322
+ end
323
+ state = `osascript -e 'tell application "iTunes" to player state as string'`.strip
324
+ if state != 'stopped'
325
+ current_track_index = `osascript -e 'tell application "iTunes" to index of current track as string'`.to_i
326
+ else
327
+ current_track_index = nil
328
+ end
329
+ @i.queue.tracks.each_with_index do |t, i|
330
+ if current_track_index && current_track_index - 1 == i
331
+ puts "%s : %s <--- currently playing" % [t.artist, t.name]
332
+ else
333
+ puts "%s : %s" % [t.artist, t.name]
334
+ end
335
+ end
336
+ end
337
+
338
+ def clear_queue
339
+ @i.clear_queue
340
+ puts "Cleared queue"
341
+ end
342
+
343
+ def start_queue
344
+ @i.stop
345
+ @i.queue.playOnce(1)
346
+ track = @i.currentTrack
347
+ puts "Playing '#{track.name}' by #{track.artist} from #{track.album}"
348
+ end
349
+
350
+ def skip
351
+ @i.nextTrack
352
+ track = @i.currentTrack
353
+ puts "Playing '#{track.name}' by #{track.artist} from #{track.album}"
354
+ end
355
+
356
+ def playpause
357
+ @i.playpause
358
+ end
359
+
360
+ def artists
361
+ rows = []
362
+ (@artists=@i.artists).keys.sort_by {|x| x.downcase}.each_with_index do |k, i|
363
+ rows << ("%s (%s tracks)" % [k, @artists[k]])
364
+ end
365
+ pager(rows)
366
+ end
367
+
368
+ def pager(rows)
369
+ out = rows.join("\n")
370
+ IO.popen("less", "w") do |less|
371
+ less.puts out
372
+ less.close_write
373
+ end
374
+ end
375
+
376
+ HELP = <<END
377
+ itunes-command
378
+
379
+ Commands:
380
+
381
+ q quit
382
+ h show commands
383
+ s <string> searches for tracks matching <string>
384
+ <track number> plays a track (assumes you've done a search and got results)
385
+ a lists all artists in the library
386
+ v show the current volume level
387
+ v <level> sets the volume level (1-100)
388
+ + <increment> increases the volume by <increment>; default is 10 steps
389
+ - <increment> decreases the volume by <increment>; default is 10 steps
390
+ x stop
391
+ " pause/play
392
+ p shows all playlists
393
+ <playlist number> shows all the tracks in a playlist
394
+ l list all tracks in the queue (which will play tracks in succession)
395
+ n <track number> put a track in the queue; can be a range, e.g. 3-5
396
+ c clear the queue
397
+ g start playing tracks in the queue
398
+ k skip to next track in queue
399
+ END
400
+
401
+ COMMANDS = { 's' => :search,
402
+ 'x' => :stop , 'play' => :play, 'v' => :volume, 'a' => :artists, '+' => :+, '-' => '-',
403
+ 'p' => :playlists, 'select_playlist' => :select_playlist, 'l' => :show_queue, 'n' => :queue,
404
+ 'c' => :clear_queue, 'g' => 'start_queue', 'k' => 'skip', '"' => :playpause }
405
+
406
+ def self.run(argv=ARGV)
407
+ i = ItunesCommand.new
408
+ puts HELP
409
+ loop do
410
+ command = Readline.readline(">> ").chomp
411
+ if command =~ /^q/
412
+ exit
413
+ elsif command =~ /^h/
414
+ puts HELP
415
+ next
416
+ end
417
+ args = command.split(' ')
418
+ if args.first =~ /\d+/
419
+ if i.playlist_mode
420
+ args.unshift 'select_playlist'
421
+ else
422
+ args.unshift 'play'
423
+ end
424
+ end
425
+ method = COMMANDS[args.shift]
426
+ if method.nil?
427
+ puts "Sorry, I don't recognize that command."
428
+ next
429
+ end
430
+ begin
431
+ args.empty? ? i.send(method) : i.send(method, args.join(' '))
432
+ rescue ArgumentError
433
+ puts "Invalid command."
434
+ end
435
+ end
436
+
437
+ end
438
+
439
+ end
440
+
441
+
442
+ if __FILE__ == $0
443
+ ItunesCommand.run ARGV
444
+ end
445
+
metadata ADDED
@@ -0,0 +1,55 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: danchoi-itunes-command
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.6.3
5
+ platform: x86-darwin-9
6
+ authors:
7
+ - Daniel Choi
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-01-20 00:00:00 -08:00
13
+ default_executable: itunes-command
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
+ - lib/itunes_command.rb
26
+ - bin/itunes-command
27
+ has_rdoc: true
28
+ homepage: http://danielchoi.com/software/itunes-command
29
+ post_install_message:
30
+ rdoc_options:
31
+ - --inline-source
32
+ - --charset=UTF-8
33
+ require_paths:
34
+ - lib
35
+ required_ruby_version: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: "0"
40
+ version:
41
+ required_rubygems_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: "0"
46
+ version:
47
+ requirements: []
48
+
49
+ rubyforge_project:
50
+ rubygems_version: 1.2.0
51
+ signing_key:
52
+ specification_version: 2
53
+ summary: Search and play iTunes tracks from the command line
54
+ test_files: []
55
+