itunes-command 1.4-universal-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 +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
+