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