vitunes 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/lib/vitunes.vim ADDED
@@ -0,0 +1,344 @@
1
+ " Vim script that add ability to search and play iTunes tracks from Vim
2
+ " Maintainer: Daniel Choi <dhchoi@gmail.com>
3
+ " License: MIT License (c) 2011 Daniel Choi
4
+ "
5
+ " Buid this path with plugin install program
6
+
7
+ if exists("g:vitunes_tool")
8
+ let s:vitunes_tool = g:vitunes_tool
9
+ else
10
+ " This is the development version (specific to D Choi's setup)
11
+ " Maybe I should make this a relative path
12
+ let s:vitunes_tool = '/Users/choi/projects/vitunes/lib/vitunes-tool-objc '
13
+ endif
14
+
15
+ let s:searchPrompt = "Search iTunes Music Library: "
16
+ let s:getPlaylistsCommand = s:vitunes_tool . "playlists"
17
+ let s:selectPlaylistPrompt = "Select playlist: "
18
+ let s:getArtistsCommand = s:vitunes_tool . "group artist | uniq "
19
+ let s:selectArtistPrompt = "Select artist: "
20
+ let s:getGenresCommand = s:vitunes_tool . "group genre | uniq "
21
+ let s:selectGenrePrompt = "Select genre: "
22
+ let s:getAlbumsCommand = s:vitunes_tool . "group album | uniq "
23
+ let s:selectAlbumPrompt = "Select album: "
24
+ let s:addTracksToPlaylistPrompt = "Add track(s) to this playlist: "
25
+
26
+ let s:currentPlaylist = ''
27
+ let s:lastPlaylist = ''
28
+ let s:selectedTrackIds = []
29
+
30
+ func! s:trimString(string)
31
+ let string = substitute(a:string, '\s\+$', '', '')
32
+ return substitute(string, '^\s\+', '', '')
33
+ endfunc
34
+
35
+ function! s:collectTrackIds(startline, endline)
36
+ let trackIds = []
37
+ let lnum = a:startline
38
+ while lnum <= a:endline
39
+ let trackId = matchstr(getline(lnum), '\d\+$')
40
+ call add(trackIds, trackId)
41
+ let lnum += 1
42
+ endwhile
43
+ return trackIds
44
+ endfunc
45
+
46
+ function! s:runCommand(command)
47
+ " echom a:command " can use for debugging
48
+ let res = system(a:command)
49
+ return res
50
+ endfunction
51
+
52
+ function! ViTunesStatusLine()
53
+ return "%<%f\ Press ? for help. "."%r%=%-14.(%l,%c%V%)\ %P"
54
+ endfunction
55
+
56
+ " the main window
57
+ function! ViTunes()
58
+ rightbelow split ViTunesBuffer
59
+ setlocal cursorline
60
+ setlocal nowrap
61
+ setlocal textwidth=0
62
+ setlocal buftype=nofile
63
+ setlocal bufhidden=hide
64
+ setlocal noswapfile
65
+ noremap <buffer> ,s :call <SID>openQueryWindow()<cr>
66
+ noremap <buffer> ,p :call <SID>openPlaylistDropdown()<cr>
67
+ noremap <buffer> ,a :call <SID>openArtistDropdown()<cr>
68
+ noremap <buffer> ,g :call <SID>openGenreDropdown()<cr>
69
+ noremap <buffer> ,A :call <SID>openAlbumDropdown()<cr>
70
+ noremap <buffer> ,c :call <SID>openAddToPlaylistDropDown()<cr>
71
+
72
+ noremap <buffer> > :call <SID>nextTrack()<cr>
73
+ noremap <buffer> < :call <SID>prevTrack()<cr>
74
+
75
+ noremap <buffer> . :call <SID>currentTrackAndPlaylist()<cr>
76
+
77
+ noremap <buffer> <Space> :call <SID>itunesControl("playpause")<cr>
78
+ noremap <buffer> - :call <SID>changeVolume("volumeDown")<cr>
79
+ noremap <buffer> + :call <SID>changeVolume("volumeUp")<cr>
80
+ noremap <buffer> = :call <SID>changeVolume("volumeUp")<cr>
81
+
82
+ " Not working yet
83
+ " noremap <buffer> <BS> :call <SID>deleteTracksFromPlaylist()<CR> "
84
+ noremap <buffer> ,i :close<CR>
85
+ noremap <buffer> ? :call <SID>help()<CR>
86
+ "noremap <buffer> <cr> <Esc>:call <SID>playTrack()<cr>
87
+ noremap <buffer> <cr> :call <SID>playTrack()<cr>
88
+ setlocal nomodifiable
89
+ setlocal statusline=%!ViTunesStatusLine()
90
+
91
+ if line('$') == 1 " buffer empty
92
+ let msg = "Welcome to ViTunes\n\nPress ? for help"
93
+ setlocal modifiable
94
+ silent! 1,$delete
95
+ silent! put =msg
96
+ silent! 1delete
97
+ setlocal nomodifiable
98
+ endif
99
+ endfunction
100
+
101
+ function! s:help()
102
+ " This just displays the README
103
+ let res = system("vitunes-help")
104
+ echo res
105
+ endfunction
106
+
107
+
108
+ function! s:itunesControl(command)
109
+ let res = s:runCommand(s:vitunes_tool . "itunes ".a:command)
110
+ echom res
111
+ endfunction
112
+
113
+ function! s:changeVolume(command)
114
+ let res = s:runCommand(s:vitunes_tool.a:command)
115
+ echom res
116
+ endfunction
117
+
118
+ function! s:playTrack()
119
+ let trackID = matchstr(getline(line('.')), '\d\+$')
120
+ if (trackID == '')
121
+ return
122
+ endif
123
+ let command = ""
124
+ if (s:currentPlaylist != '')
125
+ let command = s:vitunes_tool . "playTrackIDFromPlaylist ".trackID.' '.s:currentPlaylist
126
+ else
127
+ let command = s:vitunes_tool . "playTrackID " . trackID
128
+ endif
129
+ " echom command
130
+ call system(command)
131
+ call s:currentTrackAndPlaylist()
132
+ endfunc
133
+
134
+ function! s:hasTrackID(line)
135
+ if a:line > line('$')
136
+ return 0
137
+ end
138
+ let res = matchstr(getline(a:line), '\d\+$')
139
+ if res != ''
140
+ return 1
141
+ else
142
+ return 0
143
+ end
144
+ endfunction
145
+
146
+ " move up or down the visible list of tracks
147
+ function! s:nextTrack()
148
+ if s:hasTrackID(line('.') + 1)
149
+ normal j
150
+ call s:playTrack()
151
+ " call s:itunesControl("nextTrack")
152
+ endif
153
+ endfunction
154
+
155
+ function! s:prevTrack()
156
+ if s:hasTrackID(line('.') - 1)
157
+ normal k
158
+ call s:playTrack()
159
+ " call s:itunesControl("previousTrack")
160
+ endif
161
+ endfunction
162
+
163
+ function! s:currentTrackAndPlaylist()
164
+ let res1 = s:runCommand(s:vitunes_tool . "itunes currentTrack")
165
+ let res2 = s:runCommand(s:vitunes_tool . "itunes currentPlaylist")
166
+ echo res1 . ' | '.res2
167
+ endfunction
168
+
169
+ function! s:openQueryWindow()
170
+ leftabove split SearchTracks
171
+ setlocal textwidth=0
172
+ setlocal buftype=nofile
173
+ setlocal noswapfile
174
+ setlocal modifiable
175
+ resize 1
176
+ inoremap <buffer> <cr> <Esc>:call <SID>submitQueryOrSelection('search')<cr>
177
+ noremap <buffer> <cr> <Esc>:call <SID>submitQueryOrSelection('search')<cr>
178
+ noremap <buffer> q <Esc>:close
179
+ inoremap <buffer> <Esc> <Esc>:close<CR>
180
+ noremap <buffer> <Esc> <Esc>:close<CR>
181
+ let s:selectionPrompt = s:searchPrompt " this makes sure we parse the query correctly
182
+ call setline(1, s:searchPrompt)
183
+ normal $
184
+ call feedkeys("a", "t")
185
+ endfunction
186
+
187
+ function! GenericCompletion(findstart, base)
188
+ if a:findstart
189
+ let prompt = s:selectionPrompt
190
+ let start = len(prompt)
191
+ return start
192
+ else
193
+ if (a:base == '')
194
+ return s:selectionList
195
+ else
196
+ let res = []
197
+ " find tracks matching a:base
198
+ for m in s:selectionList
199
+ " why doesn't case insensitive flag work?
200
+ if m =~ '\c' . a:base
201
+ call add(res, m)
202
+ endif
203
+ endfor
204
+ return res
205
+ endif
206
+ endif
207
+ endfun
208
+
209
+ function! s:commonDropDownConfig()
210
+ setlocal textwidth=0
211
+ setlocal completefunc=GenericCompletion
212
+ setlocal buftype=nofile
213
+ setlocal noswapfile
214
+ setlocal modifiable
215
+ resize 1
216
+ noremap <buffer> q <Esc>:close<cr>
217
+ inoremap <buffer> <Esc> <Esc>:close<cr>
218
+ call setline(1, s:selectionPrompt)
219
+ normal $
220
+ endfunction
221
+
222
+
223
+ " Drop downs
224
+
225
+ function! s:openPlaylistDropdown()
226
+ leftabove split ChoosePlaylist
227
+ inoremap <silent> <buffer> <cr> <Esc>:call <SID>submitQueryOrSelection('playlistTracks')<CR>
228
+ let s:selectionPrompt = s:selectPlaylistPrompt
229
+ call <SID>commonDropDownConfig()
230
+ let s:selectionList = split(system(s:getPlaylistsCommand), '\n')
231
+ if (s:lastPlaylist != '')
232
+ call insert(s:selectionList, s:lastPlaylist);
233
+ call insert(s:selectionList, s:lastPlaylist);
234
+ endif
235
+ call feedkeys("a\<c-x>\<c-u>\<c-p>", 't')
236
+ endfunction
237
+
238
+ function! s:openArtistDropdown()
239
+ leftabove split ChoosePlaylist
240
+ inoremap <silent> <buffer> <cr> <Esc>:call <SID>submitQueryOrSelection('artist')<CR>
241
+ let s:selectionPrompt = s:selectArtistPrompt
242
+ call <SID>commonDropDownConfig()
243
+ let s:selectionList = split(system(s:getArtistsCommand), '\n')
244
+ call feedkeys("a\<c-x>\<c-u>\<c-p>", 't')
245
+ endfunction
246
+
247
+ function! s:openGenreDropdown()
248
+ leftabove split ChoosePlaylist
249
+ inoremap <silent> <buffer> <cr> <Esc>:call <SID>submitQueryOrSelection('genre')<CR>
250
+ let s:selectionPrompt = s:selectGenrePrompt
251
+ call <SID>commonDropDownConfig()
252
+ let s:selectionList = split(system(s:getGenresCommand), '\n')
253
+ call feedkeys("a\<c-x>\<c-u>\<c-p>", 't')
254
+ endfunction
255
+
256
+ function! s:openAlbumDropdown()
257
+ leftabove split ChoosePlaylist
258
+ inoremap <silent> <buffer> <cr> <Esc>:call <SID>submitQueryOrSelection('album')<CR>
259
+ let s:selectionPrompt = s:selectAlbumPrompt
260
+ call <SID>commonDropDownConfig()
261
+ let s:selectionList = split(system(s:getAlbumsCommand), '\n')
262
+ call feedkeys("a\<c-x>\<c-u>\<c-p>", 't')
263
+ endfunction
264
+
265
+ function! s:openAddToPlaylistDropDown() range
266
+ let s:selectedTrackIds = s:collectTrackIds(a:firstline, a:lastline)
267
+ leftabove split ChoosePlaylist
268
+ inoremap <silent> <buffer> <cr> <Esc>:call <SID>submitQueryOrSelection('addTracksToPlaylist')<CR>
269
+ let s:selectionPrompt = s:addTracksToPlaylistPrompt
270
+ echom s:selectionPrompt
271
+ call <SID>commonDropDownConfig()
272
+ let s:selectionList = split(system(s:getPlaylistsCommand), '\n')
273
+ if (s:lastPlaylist != '')
274
+ call insert(s:selectionList, s:lastPlaylist);
275
+ endif
276
+ call feedkeys("a\<c-x>\<c-u>\<c-p>", 't')
277
+ endfunction
278
+
279
+ " selection window pick or search window query
280
+ function! s:submitQueryOrSelection(command)
281
+ if (getline('.') =~ '^\s*$')
282
+ close
283
+ return
284
+ endif
285
+
286
+ let query = getline('.')[len(s:selectionPrompt):] " get(split(getline('.'), ':\s*'), 1)
287
+ close
288
+ " echom query
289
+ if (len(query) == 0 || query =~ '^\s*$')
290
+ return
291
+ endif
292
+ if a:command == 'artist'
293
+ let query = substitute(query, "'", "\'", '')
294
+ let bcommand = s:vitunes_tool."predicate ".shellescape("artist == '".query."'")
295
+ elseif a:command == 'genre'
296
+ let bcommand = s:vitunes_tool."predicate ".shellescape("genre == '".query."'")
297
+ elseif a:command == 'album'
298
+ let bcommand = s:vitunes_tool."predicate ".shellescape("album == '".query."'")
299
+ elseif a:command == 'addTracksToPlaylist'
300
+ let trackIds = join(s:selectedTrackIds, ',')
301
+ let bcommand = s:vitunes_tool.a:command." ".trackIds." ".query
302
+ else
303
+ let bcommand = s:vitunes_tool . a:command . ' ' . shellescape(query)
304
+ end
305
+ echom bcommand
306
+ let res = s:runCommand(bcommand)
307
+ if a:command == 'addTracksToPlaylist'
308
+ let res = system(s:vitunes_tool.'playlistTracks '.shellescape(query))
309
+ endif
310
+ setlocal modifiable
311
+ silent! 1,$delete
312
+ silent! put =res
313
+ silent! 1delete
314
+ setlocal nomodifiable
315
+ " position cursor at 1st track
316
+ normal 3G
317
+ if (a:command == 'playlistTracks')
318
+ let s:currentPlaylist = query
319
+ let s:lastPlaylist = query
320
+ else
321
+ let s:currentPlaylist = ''
322
+ endif
323
+ endfunction
324
+
325
+ " TODO does not work yet
326
+ function! s:deleteTracksFromPlaylist() range
327
+ if (s:currentPlaylist == '')
328
+ echom "You can't delete tracks unless you're in a playlist"
329
+ return
330
+ endif
331
+ let s:selectedTrackIds = s:collectTrackIds(a:firstline, a:lastline)
332
+ let trackIds = join(s:selectedTrackIds, ',')
333
+ let bcommand = s:vitunes_tool.'rmTracksFromPlaylist '.trackIds." ".s:currentPlaylist
334
+ echom bcommand
335
+ let res = system(bcommand)
336
+ " delete lines from buffer
337
+ echom res
338
+ endfunction
339
+
340
+ nnoremap <silent> <leader>i :call ViTunes()<cr>
341
+
342
+
343
+ let g:ViTunesLoaded = 1
344
+
data/vitunes/main.m ADDED
@@ -0,0 +1,277 @@
1
+ //
2
+ // main.m
3
+ // vitunes
4
+ //
5
+ // Created by Daniel Choi on 6/29/11.
6
+ // Copyright 2011 by Daniel Choi. All rights reserved.
7
+ //
8
+ #import "iTunes.h"
9
+ #import <Foundation/Foundation.h>
10
+
11
+ static iTunesApplication *iTunes;
12
+ static iTunesPlaylist *libraryPlaylist;
13
+ static iTunesSource *library;
14
+ int const ARTIST_COL = 26;
15
+ int const TRACK_NAME_COL = 33;
16
+ int const GENRE_COL = 15;
17
+ int const KIND_COL = 29;
18
+ int const TIME_COL = 7;
19
+ int const YEAR_COL = 4;
20
+ int const ALBUM_COL = 30;
21
+
22
+ NSString *trackListHeader() {
23
+ NSString *artist = [@"Artist" stringByPaddingToLength:ARTIST_COL withString:@" " startingAtIndex:0];
24
+ NSString *name = [@"Track" stringByPaddingToLength:TRACK_NAME_COL withString:@" " startingAtIndex:0];
25
+ NSString *genre = [@"Genre" stringByPaddingToLength:GENRE_COL withString:@" " startingAtIndex:0];
26
+ NSString *time = [@"Time" stringByPaddingToLength:TIME_COL withString:@" " startingAtIndex:0];
27
+ NSString *kind = [@"Kind" stringByPaddingToLength:KIND_COL withString:@" " startingAtIndex:0];
28
+ NSString *year = [@"Year" stringByPaddingToLength:YEAR_COL withString:@" " startingAtIndex:0];
29
+ NSString *album = [@"Album" stringByPaddingToLength:ALBUM_COL withString:@" " startingAtIndex:0];
30
+
31
+ NSString *artistSpacer = [@"-" stringByPaddingToLength:ARTIST_COL withString:@"-" startingAtIndex:0];
32
+ NSString *nameSpacer = [@"-" stringByPaddingToLength:TRACK_NAME_COL withString:@"-" startingAtIndex:0];
33
+ NSString *genreSpacer = [@"-" stringByPaddingToLength:GENRE_COL withString:@"-" startingAtIndex:0];
34
+ NSString *timeSpacer = [@"-" stringByPaddingToLength:TIME_COL withString:@"-" startingAtIndex:0];
35
+ NSString *kindSpacer = [@"-" stringByPaddingToLength:KIND_COL withString:@"-" startingAtIndex:0];
36
+ NSString *yearSpacer = [@"-" stringByPaddingToLength:YEAR_COL withString:@"-" startingAtIndex:0];
37
+ NSString *albumSpacer = [@"-" stringByPaddingToLength:ALBUM_COL withString:@"-" startingAtIndex:0];
38
+
39
+ NSString *headers = [NSString stringWithFormat: @"%@ | %@ | %@ | %@ | %@ | %@ | %@ | DatabaseID", artist, name, album, year, genre, time, kind ];
40
+ NSString *divider = [NSString stringWithFormat: @"%@-|-%@-|-%@-|-%@-|-%@-|-%@-|-%@-|-----------",
41
+ artistSpacer, nameSpacer, albumSpacer, yearSpacer, genreSpacer, timeSpacer, kindSpacer ];
42
+ return [NSString stringWithFormat:@"%@\n%@", headers, divider];
43
+ };
44
+
45
+ NSString *formatTrackForDisplay(iTunesTrack *track) {
46
+ NSString *artist = [track.artist stringByPaddingToLength:ARTIST_COL withString:@" " startingAtIndex:0];
47
+ NSString *name = [track.name stringByPaddingToLength:TRACK_NAME_COL withString:@" " startingAtIndex:0];
48
+ NSString *genre = [track.genre stringByPaddingToLength:GENRE_COL withString:@" " startingAtIndex:0];
49
+ NSString *time = [track.time stringByPaddingToLength:TIME_COL withString:@" " startingAtIndex:0];
50
+ NSString *kind = [track.kind stringByPaddingToLength:KIND_COL withString:@" " startingAtIndex:0];
51
+ NSString *year;
52
+ if (track.year != 0) {
53
+ year = [NSString stringWithFormat:@"%d", track.year];
54
+ } else {
55
+ year = @" ";
56
+ }
57
+ NSString *album = [track.album stringByPaddingToLength:ALBUM_COL withString:@" " startingAtIndex:0];
58
+ return [NSString stringWithFormat: @"%@ | %@ | %@ | %@ | %@ | %@ | %@ | %d",
59
+ artist,
60
+ name,
61
+ album,
62
+ year,
63
+ genre,
64
+ time,
65
+ kind,
66
+ track.databaseID
67
+ ];
68
+ }
69
+
70
+ SBElementArray *search(NSArray *args) {
71
+ NSString *query = [args componentsJoinedByString:@" "];
72
+ return [libraryPlaylist searchFor:query only:iTunesESrAAll];
73
+ }
74
+
75
+ NSNumber *convertNSStringToNumber(NSString *s) {
76
+ NSNumberFormatter * f = [[NSNumberFormatter alloc] init];
77
+ [f setNumberStyle:NSNumberFormatterDecimalStyle];
78
+ NSNumber *result = [f numberFromString:s];
79
+ [f release];
80
+ return result;
81
+ }
82
+
83
+ iTunesTrack *findTrackID(NSString *trackID) {
84
+ NSNumber *databaseId = convertNSStringToNumber(trackID);
85
+ NSArray *xs = [[libraryPlaylist tracks] filteredArrayUsingPredicate: [NSPredicate predicateWithFormat:@"databaseID == %@", databaseId]];
86
+ iTunesTrack* t = [xs objectAtIndex:0];
87
+ return t;
88
+ }
89
+
90
+ void playTrackID(NSString *trackID) {
91
+ iTunesTrack* t = findTrackID(trackID);
92
+ NSLog(@"Playing track: %@", [t name]);
93
+ // TODO report a missing track
94
+ [t playOnce:true]; // false would play next song on list after this one finishes
95
+ }
96
+
97
+ void playTrackIDFromPlaylist(NSString *trackID, NSString *playlistName) {
98
+ NSNumber *databaseId = convertNSStringToNumber(trackID);
99
+ iTunesPlaylist *playlist = [[library playlists] objectWithName:playlistName];
100
+ NSArray *xs = [[playlist tracks] filteredArrayUsingPredicate: [NSPredicate predicateWithFormat:@"databaseID == %@", databaseId]];
101
+ if ([xs count] == 0) {
102
+ NSLog(@"Could not find trackID: %@ in playlist: %@. Playing from Library.", trackID, playlistName);
103
+ playTrackID(trackID);
104
+ return;
105
+ } else {
106
+ iTunesTrack* t = [xs objectAtIndex:0];
107
+ NSLog(@"Playing track: %@ from playlist: %@", [t name], playlistName);
108
+ [t playOnce:false]; // play playlist continuously
109
+ }
110
+ }
111
+
112
+ void addTracksToPlaylistName(NSString *trackIds, NSString *playlistName) {
113
+ iTunesPlaylist *playlist = [[library playlists] objectWithName:playlistName];
114
+ for (NSString *trackID in [trackIds componentsSeparatedByString:@","]) {
115
+ iTunesTrack* t = findTrackID(trackID);
116
+ NSLog(@"Adding track: %@ to playlist: %@", [t name], [playlist name]);
117
+ [t duplicateTo:playlist];
118
+ }
119
+ }
120
+
121
+ // TODO This doesn't seem to work
122
+ void rmTracksFromPlaylistName(NSString *trackIds, NSString *playlistName) {
123
+ iTunesPlaylist *playlist = [[library playlists] objectWithName:playlistName];
124
+
125
+ for (NSString *trackID in [trackIds componentsSeparatedByString:@","]) {
126
+ iTunesTrack* t = findTrackID(trackID);
127
+ NSLog(@"Removing track: %@ from playlist: %@", [t name], [playlist name]);
128
+ [[playlist tracks] removeObject:t];
129
+ }
130
+
131
+ }
132
+
133
+
134
+ void groupTracksBy(NSString *property) {
135
+ // gets list of all e.g. artists, genres
136
+ // NOTE year won't work yet
137
+ // NOTE pipe the output through uniq
138
+ NSArray *results = [[[libraryPlaylist tracks] arrayByApplyingSelector:NSSelectorFromString(property)]
139
+ filteredArrayUsingPredicate: [NSPredicate predicateWithFormat:@"%@ != ''", property]];
140
+ NSArray *sortedResults = [results sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
141
+ for (NSString *s in sortedResults) {
142
+ printf("%s\n", [s cStringUsingEncoding: NSUTF8StringEncoding]);
143
+ }
144
+ }
145
+
146
+ void printTracks(NSArray *tracks) {
147
+ printf("%s\n", [trackListHeader() cStringUsingEncoding: NSUTF8StringEncoding]);
148
+ for (iTunesTrack *track in tracks) {
149
+ printf("%s\n", [formatTrackForDisplay(track) cStringUsingEncoding: NSUTF8StringEncoding]);
150
+ }
151
+ }
152
+
153
+ void playlists() {
154
+ for (iTunesPlaylist *p in [library playlists]) {
155
+ printf("%s\n", [p.name cStringUsingEncoding: NSUTF8StringEncoding]);
156
+ }
157
+ }
158
+
159
+ void playlistTracks(NSString *playlistName) {
160
+ iTunesPlaylist *playlist = [[library playlists] objectWithName:playlistName];
161
+ printTracks([playlist tracks]);
162
+ }
163
+
164
+ void playPlaylist(NSString *playlistName) {
165
+ iTunesPlaylist *playlist = [[library playlists] objectWithName:playlistName];
166
+ if (playlist == nil) {
167
+ NSLog(@"No playlist found: %@", playlistName);
168
+ }
169
+ [playlist playOnce:true];
170
+ }
171
+
172
+ void tracksMatchingPredicate(NSString *predString) {
173
+ // predicate can be something like "artist == 'U2'"
174
+ NSArray *tracks = [[libraryPlaylist tracks]
175
+ filteredArrayUsingPredicate: [NSPredicate predicateWithFormat:predString]];
176
+ printTracks(tracks);
177
+ }
178
+
179
+ // This dispatches to any methods on iTunesApplication with no parameters.
180
+ // If method returns an iTunesItem, its 'name' property will be called and
181
+ // displayed.
182
+ void itunes(NSString *command) {
183
+ SEL selector = NSSelectorFromString(command);
184
+ id result = [iTunes performSelector:selector];
185
+ if (result) {
186
+ if ([result respondsToSelector:@selector(name)]) {
187
+ NSString *s;
188
+ // Note that the real classname is ITunesTrack, not iTunesTrack;
189
+ // For current Track
190
+ if ([[result className] isEqual:@"ITunesTrack"]) {
191
+ if ([result name ] == nil) {
192
+ printf("No current track");
193
+ return;
194
+ }
195
+ s = [NSString stringWithFormat:@"\"%@\" by %@ from %@",
196
+ [((iTunesTrack *)result) name],
197
+ [((iTunesTrack *)result) artist],
198
+ [((iTunesTrack *)result) album]
199
+ ];
200
+ printf("Current track: %s", [s cStringUsingEncoding: NSUTF8StringEncoding]);
201
+ } else if ([[result className] isEqual:@"ITunesPlaylist"]) {
202
+ if ([result name ] == nil) {
203
+ printf("No current playlist");
204
+ return;
205
+ }
206
+ s = [NSString stringWithFormat:@"%@", [((iTunesPlaylist *)result) name]];
207
+ printf("Current playlist: %s", [s cStringUsingEncoding: NSUTF8StringEncoding]);
208
+ } else {
209
+ s = ((iTunesItem *)result).name;
210
+ printf("%s\n", [s cStringUsingEncoding: NSUTF8StringEncoding]);
211
+ }
212
+ }
213
+ }
214
+ }
215
+
216
+ void turnVolume(NSString *direction) {
217
+ NSInteger currentVolume = iTunes.soundVolume;
218
+ NSInteger increment = 8;
219
+ if (currentVolume < 100 && [direction isEqual:@"up"]) {
220
+ iTunes.soundVolume += increment;
221
+ } else if (currentVolume > 0 && [direction isEqual:@"down"]) {
222
+ iTunes.soundVolume -= increment;
223
+ }
224
+ printf("Changing volume %d -> %d", (int)currentVolume, (int)iTunes.soundVolume);
225
+ }
226
+
227
+
228
+ int main (int argc, const char * argv[]) {
229
+ NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
230
+ NSArray *rawArgs = [[NSProcessInfo processInfo] arguments];
231
+ NSString *action;
232
+ NSArray *args;
233
+ if ([rawArgs count] < 2) {
234
+ action = @"search";
235
+ args = [NSArray arrayWithObject:@"bach"];
236
+ } else {
237
+ action = [rawArgs objectAtIndex:1];
238
+ NSRange aRange;
239
+ aRange.location = 2;
240
+ aRange.length = [rawArgs count] - 2;
241
+ args = [rawArgs subarrayWithRange:aRange];
242
+ }
243
+ iTunes = [SBApplication applicationWithBundleIdentifier:@"com.apple.iTunes"];
244
+ library = [[iTunes sources] objectWithName:@"Library"];
245
+ libraryPlaylist = [[library playlists] objectWithName:@"Library"];
246
+
247
+ if ([action isEqual: @"search"]) {
248
+ printTracks(search(args));
249
+ } else if ([action isEqual: @"playTrackID"]) {
250
+ playTrackID([args objectAtIndex:0]);
251
+ } else if ([action isEqual: @"playTrackIDFromPlaylist"]) {
252
+ playTrackIDFromPlaylist([args objectAtIndex:0], [args objectAtIndex:1]);
253
+ } else if ([action isEqual: @"group"]) {
254
+ groupTracksBy([args objectAtIndex:0]);
255
+ } else if ([action isEqual: @"predicate"]) {
256
+ tracksMatchingPredicate([args objectAtIndex:0]);
257
+ } else if ([action isEqual: @"playlists"]) {
258
+ playlists();
259
+ } else if ([action isEqual: @"playlistTracks"]) {
260
+ playlistTracks([args objectAtIndex:0]);
261
+ } else if ([action isEqual: @"playPlaylist"]) {
262
+ playPlaylist([args objectAtIndex:0]);
263
+ } else if ([action isEqual: @"addTracksToPlaylist"]) {
264
+ // make sure to quote args
265
+ addTracksToPlaylistName([args objectAtIndex:0], [args objectAtIndex:1]);
266
+ } else if ([action isEqual: @"volumeUp"]) {
267
+ turnVolume(@"up");
268
+ } else if ([action isEqual: @"volumeDown"]) {
269
+ turnVolume(@"down");
270
+ } else if ([action isEqual: @"itunes"]) {
271
+ // argument is an action for iTunesApplication to perform
272
+ itunes([args objectAtIndex:0]);
273
+ }
274
+ [pool drain];
275
+ return 0;
276
+ }
277
+
@@ -0,0 +1,7 @@
1
+ //
2
+ // Prefix header for all source files of the 'vitunes' target in the 'vitunes' project
3
+ //
4
+
5
+ #ifdef __OBJC__
6
+ #import <Foundation/Foundation.h>
7
+ #endif