vitunes 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +11 -0
- data/NOTES +216 -0
- data/README.markdown +65 -0
- data/Rakefile +69 -0
- data/bin/vitunes +9 -0
- data/bin/vitunes-help +11 -0
- data/bin/vitunes-install +9 -0
- data/iTunes.h +507 -0
- data/lib/plugin.erb +10 -0
- data/lib/vitunes/version.rb +3 -0
- data/lib/vitunes-tool-objc +0 -0
- data/lib/vitunes.rb +29 -0
- data/lib/vitunes.vim +344 -0
- data/vitunes/main.m +277 -0
- data/vitunes/vitunes-Prefix.pch +7 -0
- data/vitunes/vitunes.1 +79 -0
- data/vitunes.gemspec +26 -0
- data/vitunes.xcodeproj/project.pbxproj +233 -0
- data/vitunes.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- metadata +87 -0
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
|
+
|