vitunes-10.5 0.4.8

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,381 @@
1
+ " Vim script that lets you 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
+ let s:vitunes_tool = 'dev/vitunes '
12
+ " Maybe I should make this a relative path
13
+ endif
14
+ if !exists("g:mapleader")
15
+ let g:mapleader = ","
16
+ endif
17
+
18
+ let s:searchPrompt = "Search iTunes Music Library: "
19
+ let s:getPlaylistsCommand = s:vitunes_tool . "playlists"
20
+ let s:selectPlaylistPrompt = "Select playlist: "
21
+ let s:getArtistsCommand = s:vitunes_tool . "group artist | uniq "
22
+ let s:selectArtistPrompt = "Select artist: "
23
+ let s:getGenresCommand = s:vitunes_tool . "group genre | uniq "
24
+ let s:selectGenrePrompt = "Select genre: "
25
+ let s:getAlbumsCommand = s:vitunes_tool . "group album | uniq "
26
+ let s:selectAlbumPrompt = "Select album: "
27
+ let s:addTracksToPlaylistPrompt = "Add track(s) to this playlist: "
28
+
29
+ let s:currentPlaylist = ''
30
+ let s:lastPlaylist = ''
31
+ let s:selectedTrackIds = []
32
+
33
+ let s:musicStoreURL = "http://www.amazon.com/gp/redirect.html?ie=UTF8&location=http%3A%2F%2Fwww.amazon.com%2FMP3-Music-Download%2Fb%3Fie%3DUTF8%26node%3D163856011%26ref_%3Dtopnav_storetab_dmusic%23&tag=instantwatche-20&linkCode=ur2&camp=1789&creative=390957"
34
+
35
+ func! s:trimString(string)
36
+ let string = substitute(a:string, '\s\+$', '', '')
37
+ return substitute(string, '^\s\+', '', '')
38
+ endfunc
39
+
40
+ function! s:collectTrackIds(startline, endline)
41
+ let trackIds = []
42
+ let lnum = a:startline
43
+ while lnum <= a:endline
44
+ let trackId = matchstr(getline(lnum), '\d\+$')
45
+ call add(trackIds, trackId)
46
+ let lnum += 1
47
+ endwhile
48
+ return trackIds
49
+ endfunc
50
+
51
+ function! s:runCommand(command)
52
+ " echom a:command " can use for debugging
53
+ let res = system(a:command)
54
+ return res
55
+ endfunction
56
+
57
+ function! ViTunesStatusLine()
58
+ return "%<%f\ Press ".g:mapleader."? for help. "."%r%=%-14.(%l,%c%V%)\ %P"
59
+ endfunction
60
+
61
+ " the main window
62
+ function! ViTunes()
63
+ rightbelow split ViTunesBuffer
64
+ setlocal cursorline
65
+ setlocal nowrap
66
+ setlocal textwidth=0
67
+ setlocal buftype=nofile
68
+ setlocal bufhidden=hide
69
+ setlocal noswapfile
70
+ noremap <buffer> <Leader>s :call <SID>openQueryWindow()<cr>
71
+ noremap <buffer> <Leader>p :call <SID>openPlaylistDropdown()<cr>
72
+ noremap <buffer> <Leader>a :call <SID>openArtistDropdown()<cr>
73
+ noremap <buffer> <Leader>g :call <SID>openGenreDropdown()<cr>
74
+ noremap <buffer> <Leader>A :call <SID>openAlbumDropdown()<cr>
75
+ noremap <buffer> <Leader>c :call <SID>openAddToPlaylistDropDown()<cr>
76
+ noremap <buffer> <BS> :call <SID>deleteTracksFromPlaylist()<CR> "
77
+ noremap <buffer> <Leader>P :call <SID>gotoCurrentPlaylist()<cr>
78
+
79
+ noremap <buffer> > :call <SID>nextTrack()<cr>
80
+ noremap <buffer> < :call <SID>prevTrack()<cr>
81
+ noremap <buffer> >> :call <SID>itunesControl("nextTrack")<cr>
82
+ noremap <buffer> << :call <SID>itunesControl("backTrack")<cr>
83
+
84
+ noremap <buffer> . :call <SID>currentTrackAndPlaylist()<cr>
85
+
86
+ noremap <buffer> <Space> :call <SID>itunesControl("playpause")<cr>
87
+ noremap <buffer> - :call <SID>changeVolume("volumeDown")<cr>
88
+ noremap <buffer> + :call <SID>changeVolume("volumeUp")<cr>
89
+ noremap <buffer> = :call <SID>changeVolume("volumeUp")<cr>
90
+
91
+ noremap <buffer> <Leader>i :close<CR>
92
+ noremap <buffer> <Leader>? :call <SID>help()<CR>
93
+ noremap <buffer> <Leader>z :call <SID>musicStore()<CR>
94
+
95
+ noremap <buffer> <cr> :call <SID>playTrack()<cr>
96
+ setlocal nomodifiable
97
+ setlocal statusline=%!ViTunesStatusLine()
98
+
99
+ command! -buffer -bar -nargs=1 NewPlaylist call <SID>newPlaylist(<f-args>)
100
+
101
+ if line('$') == 1 " buffer empty
102
+ let msg = "Welcome to ViTunes\n\nPress ".g:mapleader."? for help"
103
+ setlocal modifiable
104
+ silent! 1,$delete
105
+ silent! put =msg
106
+ silent! 1delete
107
+ setlocal nomodifiable
108
+ endif
109
+ endfunction
110
+
111
+ function! s:help()
112
+ " This just displays the README
113
+ let res = system("vitunes-help")
114
+ echo res
115
+ endfunction
116
+
117
+
118
+ function! s:itunesControl(command)
119
+ let res = s:runCommand(s:vitunes_tool . "itunes ".a:command)
120
+ echom res
121
+ endfunction
122
+
123
+ function! s:changeVolume(command)
124
+ let res = s:runCommand(s:vitunes_tool.a:command)
125
+ echom res
126
+ endfunction
127
+
128
+ function! s:playTrack()
129
+ let trackID = matchstr(getline(line('.')), '\d\+$')
130
+ if (trackID == '')
131
+ return
132
+ endif
133
+ let command = ""
134
+ if (s:currentPlaylist != '')
135
+ let command = s:vitunes_tool . "playTrackIDFromPlaylist ".trackID.' '.shellescape(s:currentPlaylist)
136
+ else
137
+ let command = s:vitunes_tool . "playTrackID " . trackID
138
+ endif
139
+ "echom command
140
+ call system(command)
141
+ call s:currentTrackAndPlaylist()
142
+ endfunc
143
+
144
+ function! s:hasTrackID(line)
145
+ if a:line > line('$')
146
+ return 0
147
+ end
148
+ let res = matchstr(getline(a:line), '\d\+$')
149
+ if res != ''
150
+ return 1
151
+ else
152
+ return 0
153
+ end
154
+ endfunction
155
+
156
+ " move up or down the visible list of tracks
157
+ function! s:nextTrack()
158
+ if s:hasTrackID(line('.') + 1)
159
+ normal j
160
+ call s:playTrack()
161
+ " call s:itunesControl("nextTrack")
162
+ endif
163
+ endfunction
164
+
165
+ function! s:prevTrack()
166
+ if s:hasTrackID(line('.') - 1)
167
+ normal k
168
+ call s:playTrack()
169
+ " call s:itunesControl("previousTrack")
170
+ endif
171
+ endfunction
172
+
173
+ function! s:currentTrackAndPlaylist()
174
+ let res1 = s:runCommand(s:vitunes_tool . "itunes currentTrack")
175
+ " fix this on obj-c side later; we just need to make sure this doesn't
176
+ " spill over to another line.
177
+ let res2 = s:runCommand(s:vitunes_tool . "itunes currentPlaylist")
178
+ echo res1[0:60] . ' | '.res2[0:30]
179
+ endfunction
180
+
181
+ function! s:openQueryWindow()
182
+ leftabove split SearchLibrary
183
+ setlocal textwidth=0
184
+ setlocal buftype=nofile
185
+ setlocal noswapfile
186
+ setlocal modifiable
187
+ resize 1
188
+ inoremap <buffer> <cr> <Esc>:call <SID>submitQueryOrSelection('search')<cr>
189
+ noremap <buffer> <cr> <Esc>:call <SID>submitQueryOrSelection('search')<cr>
190
+ noremap <buffer> q <Esc>:close
191
+ inoremap <buffer> <Esc> <Esc>:close<CR>
192
+ noremap <buffer> <Esc> <Esc>:close<CR>
193
+ let s:selectionPrompt = s:searchPrompt " this makes sure we parse the query correctly
194
+ call setline(1, s:searchPrompt)
195
+ normal $
196
+ call feedkeys("a", "t")
197
+ endfunction
198
+
199
+ function! GenericCompletion(findstart, base)
200
+ if a:findstart
201
+ let prompt = s:selectionPrompt
202
+ let start = len(prompt)
203
+ return start
204
+ else
205
+ if (a:base == '')
206
+ return s:selectionList
207
+ else
208
+ let res = []
209
+ " find tracks matching a:base
210
+ for m in s:selectionList
211
+ " why doesn't case insensitive flag work?
212
+ if m =~ '^\c' . a:base
213
+ call add(res, m)
214
+ endif
215
+ endfor
216
+ return res
217
+ endif
218
+ endif
219
+ endfun
220
+
221
+ function! s:commonDropDownConfig()
222
+ setlocal textwidth=0
223
+ setlocal completefunc=GenericCompletion
224
+ setlocal buftype=nofile
225
+ setlocal noswapfile
226
+ setlocal modifiable
227
+ resize 1
228
+ noremap <buffer> q <Esc>:close<cr>
229
+ inoremap <buffer> <Esc> <Esc>:close<cr>
230
+ call setline(1, s:selectionPrompt)
231
+ inoremap <buffer> <Tab> <C-x><C-u>
232
+ normal $
233
+ endfunction
234
+
235
+ " Drop downs
236
+ function! s:openPlaylistDropdown()
237
+ leftabove split ChoosePlaylist
238
+ inoremap <silent> <buffer> <cr> <Esc>:call <SID>submitQueryOrSelection('playlistTracks')<CR>
239
+ let s:selectionPrompt = s:selectPlaylistPrompt
240
+ call s:commonDropDownConfig()
241
+ let s:selectionList = split(system(s:getPlaylistsCommand), '\n')
242
+ if (s:lastPlaylist != '')
243
+ call insert(s:selectionList, s:lastPlaylist);
244
+ endif
245
+ call feedkeys("a\<c-x>\<c-u>\<c-p>", 't')
246
+ endfunction
247
+
248
+ function! s:openArtistDropdown()
249
+ leftabove split ChooseArtist
250
+ inoremap <silent> <buffer> <cr> <Esc>:call <SID>submitQueryOrSelection('artist')<CR>
251
+ let s:selectionPrompt = s:selectArtistPrompt
252
+ call s:commonDropDownConfig()
253
+ let s:selectionList = split(system(s:getArtistsCommand), '\n')
254
+ call feedkeys("a\<c-x>\<c-u>\<c-p>", 't')
255
+ endfunction
256
+
257
+ function! s:openGenreDropdown()
258
+ leftabove split ChooseGenre
259
+ inoremap <silent> <buffer> <cr> <Esc>:call <SID>submitQueryOrSelection('genre')<CR>
260
+ let s:selectionPrompt = s:selectGenrePrompt
261
+ call <SID>commonDropDownConfig()
262
+ let s:selectionList = split(system(s:getGenresCommand), '\n')
263
+ call feedkeys("a\<c-x>\<c-u>\<c-p>", 't')
264
+ endfunction
265
+
266
+ function! s:openAlbumDropdown()
267
+ leftabove split ChooseAlbum
268
+ inoremap <silent> <buffer> <cr> <Esc>:call <SID>submitQueryOrSelection('album')<CR>
269
+ let s:selectionPrompt = s:selectAlbumPrompt
270
+ call <SID>commonDropDownConfig()
271
+ let s:selectionList = split(system(s:getAlbumsCommand), '\n')
272
+ call feedkeys("a\<c-x>\<c-u>\<c-p>", 't')
273
+ endfunction
274
+
275
+ function! s:openAddToPlaylistDropDown() range
276
+ let s:selectedTrackIds = s:collectTrackIds(a:firstline, a:lastline)
277
+ leftabove split ChoosePlaylist
278
+ inoremap <silent> <buffer> <cr> <Esc>:call <SID>submitQueryOrSelection('addTracksToPlaylist')<CR>
279
+ let s:selectionPrompt = s:addTracksToPlaylistPrompt
280
+ echom s:selectionPrompt
281
+ call <SID>commonDropDownConfig()
282
+ let s:selectionList = split(system(s:getPlaylistsCommand), '\n')
283
+ if (s:lastPlaylist != '')
284
+ call insert(s:selectionList, s:lastPlaylist)
285
+ endif
286
+ call feedkeys("a\<c-x>\<c-u>\<c-p>", 't')
287
+ endfunction
288
+
289
+ " selection window pick or search window query
290
+ function! s:submitQueryOrSelection(command)
291
+ if (getline('.') =~ '^\s*$')
292
+ close
293
+ return
294
+ endif
295
+ let query = getline('.')[len(s:selectionPrompt):] " get(split(getline('.'), ':\s*'), 1)
296
+ close
297
+ " echom query
298
+ if (len(query) == 0 || query =~ '^\s*$')
299
+ return
300
+ endif
301
+ if a:command == 'artist'
302
+ let query = substitute(query, "'", "\'", '')
303
+ let bcommand = s:vitunes_tool."predicate ".shellescape("artist == '".query."'")
304
+ elseif a:command == 'genre'
305
+ let bcommand = s:vitunes_tool."predicate ".shellescape("genre == '".query."'")
306
+ elseif a:command == 'album'
307
+ let bcommand = s:vitunes_tool."predicate ".shellescape("album == '".query."'")
308
+ elseif a:command == 'addTracksToPlaylist'
309
+ let trackIds = join(s:selectedTrackIds, ',')
310
+ let bcommand = s:vitunes_tool.a:command." ".trackIds." ".shellescape(query)
311
+ else
312
+ let bcommand = s:vitunes_tool . a:command . ' ' . shellescape(query)
313
+ end
314
+ echom bcommand
315
+ let res = s:runCommand(bcommand)
316
+ if a:command == 'addTracksToPlaylist'
317
+ redraw
318
+ echom "Added to playlist '".query."'"
319
+ return
320
+ endif
321
+ setlocal modifiable
322
+ silent! 1,$delete
323
+ silent! put =res
324
+ silent! 1delete
325
+ setlocal nomodifiable
326
+ " position cursor at 1st track
327
+ normal 3G
328
+ if (a:command == 'playlistTracks') "
329
+ let s:currentPlaylist = query
330
+ let s:lastPlaylist = query
331
+ else
332
+ let s:currentPlaylist = ''
333
+ endif
334
+ endfunction
335
+
336
+ function! s:deleteTracksFromPlaylist() range
337
+ let s:selectedTrackIds = s:collectTrackIds(a:firstline, a:lastline)
338
+ let trackIds = join(s:selectedTrackIds, ',')
339
+ let playlist = s:lastPlaylist
340
+ let bcommand = s:vitunes_tool."deleteTracksFromPlaylist ".trackIds." ".shellescape(playlist)
341
+ let res = s:runCommand(bcommand)
342
+ setlocal modifiable
343
+ exec "silent! ".a:firstline.",".a:lastline."delete"
344
+ redraw
345
+ setlocal nomodifiable
346
+ echom bcommand
347
+ return
348
+ endfunc
349
+
350
+ function! s:newPlaylist(name)
351
+ let command = s:vitunes_tool.'newPlaylist '.shellescape(a:name)
352
+ let res = system(s:vitunes_tool.'newPlaylist '.shellescape(a:name))
353
+ echom res
354
+ endfunction
355
+
356
+ function! s:gotoCurrentPlaylist()
357
+ let playlist = s:runCommand(s:vitunes_tool . "itunes currentPlaylist")
358
+ if playlist != ''
359
+ let bcommand = s:vitunes_tool.'playlistTracks '.shellescape(playlist)
360
+ let res = s:runCommand(bcommand)
361
+ setlocal modifiable
362
+ silent! 1,$delete
363
+ silent! put =res
364
+ silent! 1delete
365
+ setlocal nomodifiable
366
+ normal 3G
367
+ let s:currentPlaylist = playlist
368
+ let s:lastPlaylist = playlist
369
+ endif
370
+ endfunction
371
+
372
+ function! s:musicStore()
373
+ call system("open ".shellescape(s:musicStoreURL))
374
+ endfunc
375
+
376
+ nnoremap <silent> <leader>i :call ViTunes()<cr>
377
+ nnoremap <silent> <leader>I :call ViTunes()<cr>:only<CR>
378
+
379
+
380
+ let g:ViTunesLoaded = 1
381
+
data/vitunes/main.m ADDED
@@ -0,0 +1,294 @@
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 *timeValue = track.time != nil ? track.time : @" ";
50
+ NSString *time = [timeValue stringByPaddingToLength:TIME_COL withString:@" " startingAtIndex:0];
51
+ NSString *kind = [track.kind stringByPaddingToLength:KIND_COL withString:@" " startingAtIndex:0];
52
+ NSString *year;
53
+ if (track.year != 0) {
54
+ year = [NSString stringWithFormat:@"%d", track.year];
55
+ } else {
56
+ year = @" ";
57
+ }
58
+ NSString *album = [track.album stringByPaddingToLength:ALBUM_COL withString:@" " startingAtIndex:0];
59
+ return [NSString stringWithFormat: @"%@ | %@ | %@ | %@ | %@ | %@ | %@ | %d",
60
+ artist,
61
+ name,
62
+ album,
63
+ year,
64
+ genre,
65
+ time,
66
+ kind,
67
+ track.databaseID
68
+ ];
69
+ }
70
+
71
+ SBElementArray *search(NSArray *args) {
72
+ NSString *query = [args componentsJoinedByString:@" "];
73
+ return [libraryPlaylist searchFor:query only:iTunesESrAAll];
74
+ }
75
+
76
+ NSNumber *convertNSStringToNumber(NSString *s) {
77
+ NSNumberFormatter * f = [[NSNumberFormatter alloc] init];
78
+ [f setNumberStyle:NSNumberFormatterDecimalStyle];
79
+ NSNumber *result = [f numberFromString:s];
80
+ [f release];
81
+ return result;
82
+ }
83
+
84
+ iTunesTrack *findTrackID(NSString *trackID) {
85
+ NSNumber *databaseId = convertNSStringToNumber(trackID);
86
+ NSArray *xs = [[libraryPlaylist tracks] filteredArrayUsingPredicate: [NSPredicate predicateWithFormat:@"databaseID == %@", databaseId]];
87
+ iTunesTrack* t = [xs objectAtIndex:0];
88
+ return t;
89
+ }
90
+
91
+ void playTrackID(NSString *trackID) {
92
+ iTunesTrack* t = findTrackID(trackID);
93
+ NSLog(@"Playing track: %@", [t name]);
94
+ // TODO report a missing track
95
+ [t playOnce:true]; // false would play next song on list after this one finishes
96
+ }
97
+
98
+ void playTrackIDFromPlaylist(NSString *trackID, NSString *playlistName) {
99
+ NSNumber *databaseId = convertNSStringToNumber(trackID);
100
+ iTunesPlaylist *playlist = [[library playlists] objectWithName:playlistName];
101
+ NSArray *xs = [[playlist tracks] filteredArrayUsingPredicate: [NSPredicate predicateWithFormat:@"databaseID == %@", databaseId]];
102
+ if ([xs count] == 0) {
103
+ NSLog(@"Could not find trackID: %@ in playlist: %@. Playing from Library.", trackID, playlistName);
104
+ playTrackID(trackID);
105
+ return;
106
+ } else {
107
+ iTunesTrack* t = [xs objectAtIndex:0];
108
+ NSLog(@"Playing track: %@ from playlist: %@", [t name], playlistName);
109
+ [t playOnce:false]; // play playlist continuously
110
+ }
111
+ }
112
+
113
+ void addTracksToPlaylistName(NSString *trackIds, NSString *playlistName) {
114
+ iTunesPlaylist *playlist = [[library playlists] objectWithName:playlistName];
115
+ for (NSString *trackID in [trackIds componentsSeparatedByString:@","]) {
116
+ iTunesTrack* t = findTrackID(trackID);
117
+ NSLog(@"Adding track: %@ to playlist: %@", [t name], [playlist name]);
118
+ [t duplicateTo:playlist];
119
+ }
120
+ }
121
+
122
+ void deleteTracksFromPlaylistName(NSString *trackIds, NSString *playlistName) {
123
+ iTunesPlaylist *playlist = [[library playlists] objectWithName:playlistName];
124
+ for (NSString *trackID in [trackIds componentsSeparatedByString:@","]) {
125
+ NSNumber *databaseId = convertNSStringToNumber(trackID);
126
+ NSArray *xs = [[playlist tracks] filteredArrayUsingPredicate: [NSPredicate predicateWithFormat:@"databaseID == %@", databaseId]];
127
+ iTunesTrack* t = [xs objectAtIndex:0];
128
+ NSLog(@"Removing track: %@ from playlist: %@", [t name], [playlist name]);
129
+ [t delete];
130
+ }
131
+ }
132
+
133
+ void groupTracksBy(NSString *property) {
134
+ // gets list of all e.g. artists, genres
135
+ // NOTE year won't work yet
136
+ // NOTE pipe the output through uniq
137
+ NSArray *results = [[[libraryPlaylist tracks] arrayByApplyingSelector:NSSelectorFromString(property)]
138
+ filteredArrayUsingPredicate: [NSPredicate predicateWithFormat:@"%@ != ''", property]];
139
+ NSArray *sortedResults = [results sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
140
+ for (NSString *s in sortedResults) {
141
+ printf("%s\n", [s cStringUsingEncoding: NSUTF8StringEncoding]);
142
+ }
143
+ }
144
+
145
+ void printTracks(NSArray *tracks) {
146
+ printf("%s\n", [trackListHeader() cStringUsingEncoding: NSUTF8StringEncoding]);
147
+ for (iTunesTrack *track in tracks) {
148
+ printf("%s\n", [formatTrackForDisplay(track) cStringUsingEncoding: NSUTF8StringEncoding]);
149
+ }
150
+ }
151
+
152
+ void playlists() {
153
+ for (iTunesPlaylist *p in [library playlists]) {
154
+ printf("%s\n", [p.name cStringUsingEncoding: NSUTF8StringEncoding]);
155
+ }
156
+ }
157
+
158
+ void playlistTracks(NSString *playlistName) {
159
+ iTunesPlaylist *playlist = [[library playlists] objectWithName:playlistName];
160
+ printTracks([playlist tracks]);
161
+ }
162
+
163
+ void playPlaylist(NSString *playlistName) {
164
+ iTunesPlaylist *playlist = [[library playlists] objectWithName:playlistName];
165
+ if (playlist == nil) {
166
+ NSLog(@"No playlist found: %@", playlistName);
167
+ }
168
+ [playlist playOnce:true];
169
+ }
170
+
171
+ void tracksMatchingPredicate(NSString *predString) {
172
+ // predicate can be something like "artist == 'U2'"
173
+ NSArray *tracks = [[libraryPlaylist tracks]
174
+ filteredArrayUsingPredicate: [NSPredicate predicateWithFormat:predString]];
175
+ printTracks(tracks);
176
+ }
177
+
178
+ // This dispatches to any methods on iTunesApplication with no parameters.
179
+ // If method returns an iTunesItem, its 'name' property will be called and
180
+ // displayed.
181
+ void itunes(NSString *command) {
182
+ SEL selector = NSSelectorFromString(command);
183
+ id result = [iTunes performSelector:selector];
184
+ if (result) {
185
+ if ([result respondsToSelector:@selector(name)]) {
186
+ NSString *s;
187
+ // Note that the real classname is ITunesTrack, not iTunesTrack;
188
+ // For current Track
189
+ if ([[result className] isEqual:@"ITunesTrack"]) {
190
+ if ([result name ] == nil) {
191
+ printf("No current track");
192
+ return;
193
+ }
194
+ s = [NSString stringWithFormat:@"\"%@\" by %@",
195
+ [((iTunesTrack *)result) name],
196
+ [((iTunesTrack *)result) artist]
197
+ ];
198
+ printf("%s", [s cStringUsingEncoding: NSUTF8StringEncoding]);
199
+ } else if ([[result className] isEqual:@"ITunesPlaylist"]) {
200
+ if ([result name ] == nil) {
201
+ printf("No current playlist");
202
+ return;
203
+ }
204
+ s = [NSString stringWithFormat:@"%@", [((iTunesPlaylist *)result) name]];
205
+ printf("%s", [s cStringUsingEncoding: NSUTF8StringEncoding]);
206
+ } else {
207
+ s = ((iTunesItem *)result).name;
208
+ printf("%s\n", [s cStringUsingEncoding: NSUTF8StringEncoding]);
209
+ }
210
+ }
211
+ }
212
+ }
213
+
214
+ void turnVolume(NSString *direction) {
215
+ NSInteger currentVolume = iTunes.soundVolume;
216
+ NSInteger increment = 8;
217
+ if (currentVolume < 100 && [direction isEqual:@"up"]) {
218
+ iTunes.soundVolume += increment;
219
+ } else if (currentVolume > 0 && [direction isEqual:@"down"]) {
220
+ iTunes.soundVolume -= increment;
221
+ }
222
+ printf("Changing volume %d -> %d", (int)currentVolume, (int)iTunes.soundVolume);
223
+ }
224
+
225
+ void newPlaylist(NSString *name) {
226
+ // note that we have to force realize it with get to check for missing
227
+ iTunesPlaylist *existingPlaylist = [[[library playlists] objectWithName:name] get];
228
+ if (existingPlaylist != nil) {
229
+ printf("%s already exists", [[existingPlaylist name] cStringUsingEncoding: NSUTF8StringEncoding]);
230
+ return;
231
+ }
232
+ NSDictionary *props = [NSDictionary dictionaryWithObject:name forKey:@"name"];
233
+ iTunesPlaylist *playlist = [[[iTunes classForScriptingClass:@"user playlist"] alloc] initWithProperties:props];
234
+ [[library playlists] addObject:playlist];
235
+ //[playlist reveal];
236
+ printf("Created new playlist: %s", [name cStringUsingEncoding:NSUTF8StringEncoding]);
237
+ }
238
+
239
+ int main (int argc, const char * argv[]) {
240
+ NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
241
+ NSArray *rawArgs = [[NSProcessInfo processInfo] arguments];
242
+ NSString *action;
243
+ NSArray *args;
244
+ if ([rawArgs count] < 2) {
245
+ action = @"search";
246
+ args = [NSArray arrayWithObject:@"bach"];
247
+ } else {
248
+ action = [rawArgs objectAtIndex:1];
249
+ NSRange aRange;
250
+ aRange.location = 2;
251
+ aRange.length = [rawArgs count] - 2;
252
+ args = [rawArgs subarrayWithRange:aRange];
253
+ }
254
+ iTunes = [SBApplication applicationWithBundleIdentifier:@"com.apple.iTunes"];
255
+ library = [[iTunes sources] objectAtIndex:0];
256
+ libraryPlaylist = [[library playlists] objectAtIndex:0];
257
+
258
+
259
+ if ([action isEqual: @"search"]) {
260
+ printTracks(search(args));
261
+ } else if ([action isEqual: @"playTrackID"]) {
262
+ playTrackID([args objectAtIndex:0]);
263
+ } else if ([action isEqual: @"playTrackIDFromPlaylist"]) {
264
+ playTrackIDFromPlaylist([args objectAtIndex:0], [args objectAtIndex:1]);
265
+ } else if ([action isEqual: @"group"]) {
266
+ groupTracksBy([args objectAtIndex:0]);
267
+ } else if ([action isEqual: @"predicate"]) {
268
+ tracksMatchingPredicate([args objectAtIndex:0]);
269
+ } else if ([action isEqual: @"playlists"]) {
270
+ playlists();
271
+ } else if ([action isEqual: @"playlistTracks"]) {
272
+ playlistTracks([args objectAtIndex:0]);
273
+ } else if ([action isEqual: @"playPlaylist"]) {
274
+ playPlaylist([args objectAtIndex:0]);
275
+ } else if ([action isEqual: @"addTracksToPlaylist"]) {
276
+ // make sure to quote first arg
277
+ addTracksToPlaylistName([args objectAtIndex:0], [args objectAtIndex:1]);
278
+ } else if ([action isEqual: @"deleteTracksFromPlaylist"]) {
279
+ // make sure to quote first arg
280
+ deleteTracksFromPlaylistName([args objectAtIndex:0], [args objectAtIndex:1]);
281
+ } else if ([action isEqual: @"volumeUp"]) {
282
+ turnVolume(@"up");
283
+ } else if ([action isEqual: @"volumeDown"]) {
284
+ turnVolume(@"down");
285
+ } else if ([action isEqual: @"newPlaylist"]) {
286
+ newPlaylist([args objectAtIndex:0]);
287
+ } else if ([action isEqual: @"itunes"]) {
288
+ // argument is an action for iTunesApplication to perform
289
+ itunes([args objectAtIndex:0]);
290
+ }
291
+ [pool drain];
292
+ return 0;
293
+ }
294
+
@@ -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