vitunes 0.0.3

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